diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java index 79ba28bef..9e8e28a3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java @@ -18,6 +18,14 @@ */ package org.apache.fineract.infrastructure.core.exception; +import com.google.common.io.CharStreams; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Exception with multiple root causes. * @@ -25,21 +33,69 @@ package org.apache.fineract.infrastructure.core.exception; * each of which could fail, but where we don't want to fail immediately but continue, and * then fail at end. * - *

The failures MUST each be logged within the loop, as they occur; this exception is - * only thrown to propagate the failure, but will not contain and re-log the details. + *

The failures should typically also each be logged within the loop, as they occur; this exception is + * only thrown to propagate the failure, and the caller may or may not log this with the details. + * + *

Originally inspired by org.junit.runners.model.MultipleFailureException

. * * @author Michael Vorburger.ch */ public class MultiException extends Exception { - private final int n; + private final List throwables; - public MultiException(int n) { - super(n + "x failures occured here (details have been previously logged"); - this.n = n; + public MultiException(List problems) { + super("MultiException with " + problems.size() + " contained causes (details available)"); + if (problems.isEmpty()) { throw new IllegalArgumentException("List of Throwables must not be empty"); } + this.throwables = new ArrayList<>(problems); } - public int getCausesSize() { - return n; + public List getCauses() { + return Collections.unmodifiableList(throwables); + } + + @Override + public String getMessage() { + int i = 0; + StringBuilder sb = new StringBuilder(super.getMessage()); + for (Throwable e : throwables) { + sb.append("\n "); + sb.append(++i); + sb.append(". "); + Writer w = CharStreams.asWriter(sb); + e.printStackTrace(new PrintWriter(w, true)); + } + sb.append("\n which was itself thrown.."); + return sb.toString(); + } + + @Override + public void printStackTrace() { + System.err.println(super.getMessage()); + int i = 0; + for (Throwable e : throwables) { + System.err.print(++i + "."); + e.printStackTrace(); + } + } + + @Override + public void printStackTrace(PrintStream s) { + s.println(super.getMessage()); + int i = 0; + for (Throwable e : throwables) { + s.print(++i + "."); + e.printStackTrace(s); + } + } + + @Override + public void printStackTrace(PrintWriter s) { + s.println(super.getMessage()); + int i = 0; + for (Throwable e : throwables) { + s.print(++i + "."); + e.printStackTrace(s); + } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java index c66b9d612..02dc85776 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java @@ -18,11 +18,16 @@ */ package org.apache.fineract.infrastructure.jobs.exception; +import java.util.List; import org.apache.fineract.infrastructure.core.exception.MultiException; public class JobExecutionException extends MultiException { - public JobExecutionException(int n) { - super(n); + public JobExecutionException(List problems) { + super(problems); + } + + public JobExecutionException(MultiException multiException) { + super(multiException.getCauses()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java index a65bfec1b..9594b43e6 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java @@ -24,8 +24,10 @@ import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAcc import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -193,7 +195,7 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst public void executeStandingInstructions() throws JobExecutionException { Collection instructionDatas = this.standingInstructionReadPlatformService .retrieveAll(StandingInstructionStatus.ACTIVE.getValue()); - final StringBuilder sb = new StringBuilder(); + List errors = new ArrayList<>(); for (StandingInstructionData data : instructionDatas) { boolean isDueForTransfer = false; AccountTransferRecurrenceType recurrenceType = data.recurrenceType(); @@ -240,7 +242,7 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null, data .transferType().getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); - final boolean transferCompleted = transferAmount(sb, accountTransferDTO, data.getId()); + final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId()); if(transferCompleted){ final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?"; @@ -249,14 +251,10 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst } } - if (sb.length() > 0) { - logger.error("executeStandingInstructions (transferAmount) encountered failure/s: {}", sb.toString()); - // This is a bit of a hack (related to https://issues.apache.org/jira/browse/FINERACT-858) and could be improved.. - throw new JobExecutionException(123456789); - } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } - private boolean transferAmount(final StringBuilder sb, final AccountTransferDTO accountTransferDTO, final Long instructionId) { + private boolean transferAmount(final List errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) { boolean transferCompleted = true; StringBuilder errorLog = new StringBuilder(); StringBuilder updateQuery = new StringBuilder( @@ -264,24 +262,20 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst try { this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } catch (final PlatformApiDataValidationException e) { - sb.append("Validation exception while trasfering funds for standing Instruction id").append(instructionId).append(" from ") - .append(accountTransferDTO.getFromAccountId()).append(" to ").append(accountTransferDTO.getToAccountId()) - .append("--------"); + errors.add(new Exception("Validation exception while transfering funds for standing Instruction id" + instructionId + " from " + + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e)); errorLog.append("Validation exception while trasfering funds " + e.getDefaultUserMessage()); } catch (final InsufficientAccountBalanceException e) { - sb.append("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id").append(instructionId) - .append(" from ").append(accountTransferDTO.getFromAccountId()).append(" to ") - .append(accountTransferDTO.getToAccountId()).append("--------"); + errors.add(new Exception("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id" + + instructionId + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e)); errorLog.append("InsufficientAccountBalance Exception "); } catch (final AbstractPlatformServiceUnavailableException e) { - sb.append("Platform exception while trasfering funds for standing Instruction id").append(instructionId).append(" from ") - .append(accountTransferDTO.getFromAccountId()).append(" to ").append(accountTransferDTO.getToAccountId()) - .append("--------"); + errors.add(new Exception("Platform exception while trasfering funds for standing Instruction id" + instructionId + " from " + + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e)); errorLog.append("Platform exception while trasfering funds " + e.getDefaultUserMessage()); } catch (Exception e) { - sb.append("Exception while trasfering funds for standing Instruction id").append(instructionId).append(" from ") - .append(accountTransferDTO.getFromAccountId()).append(" to ").append(accountTransferDTO.getToAccountId()) - .append("--------"); + errors.add(new Exception("Unhandled System Exception while trasfering funds for standing Instruction id" + instructionId + + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e)); errorLog.append("Exception while trasfering funds " + e.getMessage()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java index 284b692c0..5798c962b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.fineract.infrastructure.core.exception.MultiException; import org.apache.fineract.infrastructure.jobs.annotation.CronTarget; @@ -63,16 +64,16 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic } } - int errors = 0; + List errors = new ArrayList<>(); for (Map.Entry> mapEntry : loanDataMap.entrySet()) { try { this.loanAccrualWritePlatformService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue()); } catch (Exception e) { LOG.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e); - ++errors; + errors.add(e); } } - if (errors > 0) { throw new JobExecutionException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } @Override @@ -81,18 +82,18 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic try { addPeriodicAccruals(LocalDate.now()); } catch (MultiException e) { - throw new JobExecutionException(e.getCausesSize()); + throw new JobExecutionException(e); } } @Override - public void addPeriodicAccruals(final LocalDate tilldate) throws MultiException { + public void addPeriodicAccruals(final LocalDate tilldate) throws JobExecutionException { Collection loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate); addPeriodicAccruals(tilldate, loanScheduleAccrualDatas); } @Override - public void addPeriodicAccruals(final LocalDate tilldate, Collection loanScheduleAccrualDatas) throws MultiException { + public void addPeriodicAccruals(final LocalDate tilldate, Collection loanScheduleAccrualDatas) throws JobExecutionException { Map> loanDataMap = new HashMap<>(); for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) { if (loanDataMap.containsKey(accrualData.getLoanId())) { @@ -104,16 +105,16 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic } } - int errors = 0; + List errors = new ArrayList<>(); for (Map.Entry> mapEntry : loanDataMap.entrySet()) { try { this.loanAccrualWritePlatformService.addPeriodicAccruals(tilldate, mapEntry.getKey(), mapEntry.getValue()); } catch (Exception e) { LOG.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e); - ++errors; + errors.add(e); } } - if (errors > 0) { throw new MultiException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } @Override @@ -121,16 +122,16 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic public void addPeriodicAccrualsForLoansWithIncomePostedAsTransactions() throws JobExecutionException { Collection loanIds = this.loanReadPlatformService.retrieveLoanIdsWithPendingIncomePostingTransactions(); if(loanIds != null && loanIds.size() > 0){ - int errors = 0; + List errors = new ArrayList<>(); for (Long loanId : loanIds) { try { this.loanAccrualWritePlatformService.addIncomeAndAccrualTransactions(loanId); } catch (Exception e) { LOG.error("Failed to add income and accrual transaction for loan {}", loanId, e); - ++errors; + errors.add(e); } } - if (errors > 0) { throw new JobExecutionException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java index b785700f5..03edf22d8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java @@ -95,7 +95,7 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { } } - int numberOfErrors = 0; + List exceptions = new ArrayList<>(); for (final Long loanId : overdueScheduleData.keySet()) { try { this.loanWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId)); @@ -104,17 +104,17 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { final List errors = e.getErrors(); for (final ApiParameterError error : errors) { logger.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId, error.getDeveloperMessage(), e); - ++numberOfErrors; } + exceptions.add(e); } catch (final AbstractPlatformDomainRuleException e) { logger.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId, e.getDefaultUserMessage(), e); - ++numberOfErrors; + exceptions.add(e); } catch (Exception e) { logger.error("Apply Charges due for overdue loans failed for account {}", loanId, e); - ++numberOfErrors; + exceptions.add(e); } } - if (numberOfErrors > 0) { throw new JobExecutionException(numberOfErrors); } + if (!exceptions.isEmpty()) { throw new JobExecutionException(exceptions); } } } @@ -129,7 +129,7 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { .fetchLoansForInterestRecalculation(); int i = 0; if (!loanIds.isEmpty()) { - int errors = 0; + List errors = new ArrayList<>(); for (Long loanId : loanIds) { logger.info("recalculateInterest: Loan ID = {}", loanId); Integer numberOfRetries = 0; @@ -143,7 +143,7 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { // Fail if the transaction has been retried for maxNumberOfRetries if (numberOfRetries >= maxNumberOfRetries) { logger.error("Recalulate interest job has been retried for the max allowed attempts of {} and will be rolled back", numberOfRetries); - ++errors; + errors.add(exception); break; } // Else sleep for a random time (between 1 to 10 seconds) and continue @@ -154,19 +154,19 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { numberOfRetries = numberOfRetries + 1; } catch (InterruptedException e) { logger.error("Interest recalculation for loans retry failed due to InterruptedException", e) ; - ++errors; + errors.add(e); break; } } catch (Exception e) { logger.error("Interest recalculation for loans failed for account {}", loanId, e); numberOfRetries = maxNumberOfRetries + 1; - ++errors; + errors.add(e); } i++; } logger.info("recalculateInterest: Loans count {}", i); } - if (errors > 0) { throw new JobExecutionException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 9f549563b..d92242165 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -181,7 +181,6 @@ import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; -import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException; import org.apache.fineract.portfolio.transfer.api.TransferApiConstants; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; @@ -325,27 +324,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public CommandProcessingResult disburseGLIMLoan(final Long loanId, final JsonCommand command) - { - final Long parentLoanId=loanId; - GroupLoanIndividualMonitoringAccount parentLoan=glimRepository.findById(parentLoanId).get(); - List childLoans=this.loanRepository.findByGlimId(loanId); - CommandProcessingResult result=null; - int count=0; - for(Loan loan:childLoans) - { - result=disburseLoan(loan.getId(),command,false); - if(result.getLoanId()!=null) - { - count++; - // if all the child loans are approved, mark the parent loan as approved - if(count==parentLoan.getChildAccountsCount()) - { - parentLoan.setLoanStatus(LoanStatus.ACTIVE.getValue()); - glimRepository.save(parentLoan); - } - } - } - return result; + { + final Long parentLoanId=loanId; + GroupLoanIndividualMonitoringAccount parentLoan=glimRepository.findById(parentLoanId).get(); + List childLoans=this.loanRepository.findByGlimId(loanId); + CommandProcessingResult result=null; + int count=0; + for(Loan loan:childLoans) + { + result=disburseLoan(loan.getId(),command,false); + if(result.getLoanId()!=null) + { + count++; + // if all the child loans are approved, mark the parent loan as approved + if(count==parentLoan.getChildAccountsCount()) + { + parentLoan.setLoanStatus(LoanStatus.ACTIVE.getValue()); + glimRepository.save(parentLoan); + } + } + } + return result; } @Transactional @@ -772,27 +771,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult undoGLIMLoanDisbursal(final Long loanId, final JsonCommand command) { - //GroupLoanIndividualMonitoringAccount glimAccount=glimRepository.findOne(loanId); - final Long parentLoanId=loanId; - GroupLoanIndividualMonitoringAccount parentLoan=glimRepository.findById(parentLoanId).get(); - List childLoans=this.loanRepository.findByGlimId(loanId); - CommandProcessingResult result=null; - int count=0; - for(Loan loan:childLoans) - { - result=undoLoanDisbursal(loan.getId(),command); - if(result.getLoanId()!=null) - { - count++; - // if all the child loans are approved, mark the parent loan as approved - if(count==parentLoan.getChildAccountsCount()) - { - parentLoan.setLoanStatus(LoanStatus.APPROVED.getValue()); - glimRepository.save(parentLoan); - } - } - } - return result; + //GroupLoanIndividualMonitoringAccount glimAccount=glimRepository.findOne(loanId); + final Long parentLoanId=loanId; + GroupLoanIndividualMonitoringAccount parentLoan=glimRepository.findById(parentLoanId).get(); + List childLoans=this.loanRepository.findByGlimId(loanId); + CommandProcessingResult result=null; + int count=0; + for(Loan loan:childLoans) + { + result=undoLoanDisbursal(loan.getId(),command); + if(result.getLoanId()!=null) + { + count++; + // if all the child loans are approved, mark the parent loan as approved + if(count==parentLoan.getChildAccountsCount()) + { + parentLoan.setLoanStatus(LoanStatus.APPROVED.getValue()); + glimRepository.save(parentLoan); + } + } + } + return result; } @Transactional @@ -854,28 +853,28 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf public CommandProcessingResult makeGLIMLoanRepayment(final Long loanId, final JsonCommand command) { - final Long parentLoanId=loanId; + final Long parentLoanId=loanId; - GroupLoanIndividualMonitoringAccount parentLoan=glimRepository.findById(parentLoanId).get(); + glimRepository.findById(parentLoanId).get(); - JsonArray repayments= command.arrayOfParameterNamed("formDataArray"); - JsonCommand childCommand=null; - CommandProcessingResult result=null; - JsonObject jsonObject=null; + JsonArray repayments= command.arrayOfParameterNamed("formDataArray"); + JsonCommand childCommand=null; + CommandProcessingResult result=null; + JsonObject jsonObject=null; - Long[] childLoanId=new Long[repayments.size()]; - for (int i = 0; i < repayments.size(); i++) { - jsonObject=repayments.get(i).getAsJsonObject(); - LOG.info("{}",jsonObject.toString()); - childLoanId[i]=jsonObject.get("loanId").getAsLong(); - } - int j=0; - for(JsonElement element:repayments ) - { - childCommand=JsonCommand.fromExistingCommand(command,element); - result=makeLoanRepayment(childLoanId[j++],childCommand,false); - } - return result; + Long[] childLoanId=new Long[repayments.size()]; + for (int i = 0; i < repayments.size(); i++) { + jsonObject=repayments.get(i).getAsJsonObject(); + LOG.info("{}",jsonObject.toString()); + childLoanId[i]=jsonObject.get("loanId").getAsLong(); + } + int j=0; + for(JsonElement element:repayments ) + { + childCommand=JsonCommand.fromExistingCommand(command,element); + result=makeLoanRepayment(childLoanId[j++],childCommand,false); + } + return result; } @Transactional @@ -1868,7 +1867,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Collection chargeDatas = this.loanChargeReadPlatformService.retrieveLoanChargesForFeePayment( ChargePaymentMode.ACCOUNT_TRANSFER.getValue(), LoanStatus.ACTIVE.getValue()); final boolean isRegularTransaction = true; - int errors = 0; + List errors = new ArrayList<>(); if (chargeDatas != null) { for (final LoanChargeData chargeData : chargeDatas) { if (chargeData.isInstallmentFee()) { @@ -1889,9 +1888,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf null, LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); - if (!transferFeeCharge(accountTransferDTO)) { - ++errors; - } + transferFeeCharge(accountTransferDTO, errors); } } } else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(DateUtils.getLocalDateOfTenant())) { @@ -1905,25 +1902,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); - if (!transferFeeCharge(accountTransferDTO)) { - ++errors; - } + transferFeeCharge(accountTransferDTO, errors); } } } - if (errors > 0) { throw new JobExecutionException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } - private boolean transferFeeCharge(final AccountTransferDTO accountTransferDTO) { + private void transferFeeCharge(final AccountTransferDTO accountTransferDTO, List errors) { try { this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); - return true; - } catch (final PlatformApiDataValidationException e) { - LOG.error("Validation exception while paying charge {} for loan id {}", accountTransferDTO.getChargeId(), accountTransferDTO.getToAccountId(), e); - return false; - } catch (final InsufficientAccountBalanceException e) { - LOG.error("InsufficientAccountBalanceException while paying charge {} for loan id {}", accountTransferDTO.getChargeId(), accountTransferDTO.getToAccountId(), e); - return false; + } catch (RuntimeException e) { + LOG.error("Exception while paying charge {} for loan id {}", accountTransferDTO.getChargeId(), accountTransferDTO.getToAccountId(), e); + errors.add(e); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java index 93f6d9819..58ef5bc1c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java @@ -18,7 +18,9 @@ */ package org.apache.fineract.portfolio.loanaccount.service; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; @@ -56,7 +58,7 @@ public class RecalculateInterestPoster implements Callable { int i = 0; if (!loanIds.isEmpty()) { - int errors = 0; + List errors = new ArrayList<>(); for (Long loanId : loanIds) { logger.info("Loan ID {}", loanId); Integer numberOfRetries = 0; @@ -70,7 +72,7 @@ public class RecalculateInterestPoster implements Callable { // Fail if the transaction has been retired for maxNumberOfRetries if (numberOfRetries >= maxNumberOfRetries) { logger.error("Recalulate interest job has been retried for the max allowed attempts of {} and will be rolled back", numberOfRetries); - ++errors; + errors.add(exception); break; } // Else sleep for a random time (between 1 to 10 seconds) and continue @@ -81,19 +83,19 @@ public class RecalculateInterestPoster implements Callable { numberOfRetries = numberOfRetries + 1; } catch (InterruptedException e) { logger.error("Interest recalculation for loans retry failed due to InterruptedException", e) ; - ++errors; + errors.add(e); break; } } catch (Exception e) { logger.error("Interest recalculation for loans failed for account {}", loanId, e); numberOfRetries = maxNumberOfRetries + 1; - ++errors; + errors.add(e); } i++; } logger.info("Loans count {}", i); } - if (errors > 0) { throw new JobExecutionException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } return null; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index 09601dc9f..c1c5fc565 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -575,20 +575,20 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @Override @CronTarget(jobName = JobName.TRANSFER_INTEREST_TO_SAVINGS) public void transferInterestToSavings() throws JobExecutionException { - int errors = 0; + List errors = new ArrayList<>(); Collection accountTrasferData = this.depositAccountReadPlatformService.retrieveDataForInterestTransfer(); for (AccountTransferDTO accountTransferDTO : accountTrasferData) { try { this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } catch (final PlatformApiDataValidationException e) { LOG.error("Validation exception while trasfering Interest from {} to {}", accountTransferDTO.getFromAccountId(), accountTransferDTO.getToAccountId(), e); - ++errors; + errors.add(e); } catch (final InsufficientAccountBalanceException e) { LOG.error("InsufficientAccountBalanceException while trasfering Interest from {} to {} ", accountTransferDTO.getFromAccountId(), accountTransferDTO.getToAccountId(), e); - ++errors; + errors.add(e); } } - if (errors > 0) { throw new JobExecutionException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java index 0e3cc7f44..67b7733ef 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java @@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.savings.service; import static org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType.ACTIVE; +import java.util.ArrayList; import java.util.List; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.jobs.annotation.CronTarget; @@ -62,7 +63,7 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService { int page = 0; Integer initialSize = 500; Integer totalPageSize = 0; - int errors = 0; + List errors = new ArrayList<>(); do { PageRequest pageRequest = PageRequest.of(page, initialSize); Page savingsAccounts = this.savingsAccountRepository.findByStatus(ACTIVE.getValue(), pageRequest); @@ -74,14 +75,14 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService { this.savingsAccountWritePlatformService.postInterest(savingsAccount, postInterestAsOn, transactionDate); } catch (Exception e) { LOG.error("Failed to post interest for Savings with id {}", savingsAccount.getId(), e); - ++errors; + errors.add(e); } } page++; totalPageSize = savingsAccounts.getTotalPages(); } while (page < totalPageSize); - if (errors > 0) { throw new JobExecutionException(errors); } + if (!errors.isEmpty()) { throw new JobExecutionException(errors); } } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java index 20e3fc66a..3d0a705c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java @@ -21,6 +21,7 @@ package org.apache.fineract.scheduledjobs.service; import java.math.BigDecimal; import java.math.BigInteger; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; @@ -239,20 +240,20 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService @CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES) public void applyDueChargesForSavings() throws JobExecutionException { final Collection chargesDueData = this.savingsAccountChargeReadPlatformService.retrieveChargesWithDue(); - int numberOfErrors = 0; + List exceptions = new ArrayList<>(); for (final SavingsAccountAnnualFeeData savingsAccountReference : chargesDueData) { try { this.savingsAccountWritePlatformService.applyChargeDue(savingsAccountReference.getId(), savingsAccountReference.getAccountId()); } catch (final PlatformApiDataValidationException e) { + exceptions.add(e); final List errors = e.getErrors(); for (final ApiParameterError error : errors) { logger.error("Apply Charges due for savings failed for account {} with message: {}", savingsAccountReference.getAccountNo(), error.getDeveloperMessage(), e); - ++numberOfErrors; } } } logger.info("{}: Savings accounts affected by update: {}", ThreadLocalContextUtil.getTenant().getName(), chargesDueData.size()); - if (numberOfErrors > 0) { throw new JobExecutionException(numberOfErrors); } + if (!exceptions.isEmpty()) { throw new JobExecutionException(exceptions); } } @Transactional @@ -372,7 +373,7 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService @Override @CronTarget(jobName = JobName.POST_DIVIDENTS_FOR_SHARES) public void postDividends() throws JobExecutionException { - int numberOfErrors = 0; + List exceptions = new ArrayList<>(); List> dividendDetails = this.shareAccountDividendReadPlatformService.retriveDividendDetailsForPostDividents(); for (Map dividendMap : dividendDetails) { Long id = null ; @@ -388,19 +389,20 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService try { this.shareAccountSchedularService.postDividend(id, savingsId); } catch (final PlatformApiDataValidationException e) { + exceptions.add(e); final List errors = e.getErrors(); for (final ApiParameterError error : errors) { - logger.error("Post Dividends to savings failed due to ApiParameterError for Divident detail Id: {} and savings Id: {} with message: {}", - new Object[] { id, savingsId, error.getDeveloperMessage(), e }); - ++numberOfErrors; + logger.error( + "Post Dividends to savings failed due to ApiParameterError for Divident detail Id: {} and savings Id: {} with message: {}", + id, savingsId, error.getDeveloperMessage(), e); } } catch (final Exception e) { logger.error("Post Dividends to savings failed for Divident detail Id: {} and savings Id: {}", id, savingsId, e); - ++numberOfErrors; + exceptions.add(e); } } - if (numberOfErrors > 0) { throw new JobExecutionException(numberOfErrors); } + if (!exceptions.isEmpty()) { throw new JobExecutionException(exceptions); } } @Override diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/exception/MultiExceptionTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/exception/MultiExceptionTest.java new file mode 100644 index 000000000..dbf874843 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/exception/MultiExceptionTest.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.junit.Assert.assertThrows; + +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class MultiExceptionTest { + + Logger logger = LoggerFactory.getLogger(MultiExceptionTest.class); + + @Test() + void testEmpty() throws MultiException { + assertThrows(IllegalArgumentException.class, () -> { throw new MultiException(Collections.emptyList()); }); + } + + @Test() + void test() throws MultiException { + List causes = List.of(new IllegalArgumentException(), new IllegalStateException()); + MultiException e = new MultiException(causes); + logger.warn("Biep, bieb", e); + // Uncomment to see JUnit UI: + // throw e; + } +}