rewrite MultiException to actually contain all causes (FINERACT-858) (#855)

This commit is contained in:
Michael Vorburger ⛑️
2020-05-15 21:30:33 +01:00
committed by GitHub
parent 2aca64056a
commit 39675c9a23
11 changed files with 249 additions and 151 deletions
@@ -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.
*
* <p>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.
* <p>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.
*
* <p>Originally inspired by org.junit.runners.model.MultipleFailureException</p>.
*
* @author Michael Vorburger.ch <mike@vorburger.ch>
*/
public class MultiException extends Exception {
private final int n;
private final List<Throwable> throwables;
public MultiException(int n) {
super(n + "x failures occured here (details have been previously logged");
this.n = n;
public MultiException(List<Throwable> 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<Throwable> 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);
}
}
}
@@ -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<Throwable> problems) {
super(problems);
}
public JobExecutionException(MultiException multiException) {
super(multiException.getCauses());
}
}
@@ -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<StandingInstructionData> instructionDatas = this.standingInstructionReadPlatformService
.retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
final StringBuilder sb = new StringBuilder();
List<Throwable> 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<Throwable> 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());
}
@@ -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<Throwable> errors = new ArrayList<>();
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> 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<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate);
addPeriodicAccruals(tilldate, loanScheduleAccrualDatas);
}
@Override
public void addPeriodicAccruals(final LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws MultiException {
public void addPeriodicAccruals(final LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws JobExecutionException {
Map<Long, Collection<LoanScheduleAccrualData>> 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<Throwable> errors = new ArrayList<>();
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> 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<Long> loanIds = this.loanReadPlatformService.retrieveLoanIdsWithPendingIncomePostingTransactions();
if(loanIds != null && loanIds.size() > 0){
int errors = 0;
List<Throwable> 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); }
}
}
}
@@ -95,7 +95,7 @@ public class LoanSchedularServiceImpl implements LoanSchedularService {
}
}
int numberOfErrors = 0;
List<Throwable> 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<ApiParameterError> 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<Throwable> 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); }
}
}
@@ -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<Loan> 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<Loan> 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<Loan> 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<Loan> 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<LoanChargeData> chargeDatas = this.loanChargeReadPlatformService.retrieveLoanChargesForFeePayment(
ChargePaymentMode.ACCOUNT_TRANSFER.getValue(), LoanStatus.ACTIVE.getValue());
final boolean isRegularTransaction = true;
int errors = 0;
List<Throwable> 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<Throwable> 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);
}
}
@@ -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<Void> {
int i = 0;
if (!loanIds.isEmpty()) {
int errors = 0;
List<Throwable> errors = new ArrayList<>();
for (Long loanId : loanIds) {
logger.info("Loan ID {}", loanId);
Integer numberOfRetries = 0;
@@ -70,7 +72,7 @@ public class RecalculateInterestPoster implements Callable<Void> {
// 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<Void> {
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;
}
@@ -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<Throwable> errors = new ArrayList<>();
Collection<AccountTransferDTO> 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
@@ -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<Throwable> errors = new ArrayList<>();
do {
PageRequest pageRequest = PageRequest.of(page, initialSize);
Page<SavingsAccount> 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
@@ -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<SavingsAccountAnnualFeeData> chargesDueData = this.savingsAccountChargeReadPlatformService.retrieveChargesWithDue();
int numberOfErrors = 0;
List<Throwable> 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<ApiParameterError> 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<Throwable> exceptions = new ArrayList<>();
List<Map<String, Object>> dividendDetails = this.shareAccountDividendReadPlatformService.retriveDividendDetailsForPostDividents();
for (Map<String, Object> 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<ApiParameterError> 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
@@ -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<Throwable> causes = List.of(new IllegalArgumentException(), new IllegalStateException());
MultiException e = new MultiException(causes);
logger.warn("Biep, bieb", e);
// Uncomment to see JUnit UI:
// throw e;
}
}