Implemented waiting for ledger creation before proceeding to account creation.
diff --git a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
index 68461f4..2aedaa3 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -31,6 +31,7 @@
import io.mifos.portfolio.api.v1.domain.*;
import io.mifos.portfolio.api.v1.events.*;
import io.mifos.portfolio.service.config.PortfolioServiceConfiguration;
+import io.mifos.portfolio.service.internal.util.AccountingListener;
import io.mifos.portfolio.service.internal.util.RhythmAdapter;
import org.junit.*;
import org.junit.runner.RunWith;
@@ -41,6 +42,7 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
@@ -124,6 +126,9 @@
@MockBean
CustomerManager customerManager;
+ @SpyBean
+ AccountingListener accountingListener;
+
@SuppressWarnings("SpringAutowiredFieldsWarningInspection")
@Autowired
@Qualifier(LOGGER_NAME)
@@ -132,7 +137,7 @@
@Before
public void prepTest() {
userContext = this.tenantApplicationSecurityEnvironment.createAutoUserContext(TEST_USER);
- AccountingFixture.mockAccountingPrereqs(ledgerManager);
+ AccountingFixture.mockAccountingPrereqs(ledgerManager, accountingListener);
Mockito.doReturn(true).when(customerManager).isCustomerInGoodStanding(Fixture.CUSTOMER_IDENTIFIER);
}
diff --git a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
index feaee49..eb00848 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -19,7 +19,9 @@
import io.mifos.accounting.api.v1.domain.*;
import io.mifos.core.api.util.NotFoundException;
import io.mifos.core.lang.DateConverter;
+import io.mifos.core.lang.TenantContextHolder;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.portfolio.service.internal.util.AccountingListener;
import org.hamcrest.Description;
import org.junit.Assert;
import org.mockito.AdditionalMatchers;
@@ -477,10 +479,17 @@
}
private static class CreateLedgerAnswer implements Answer {
+ private final AccountingListener accountingListener;
+
+ public CreateLedgerAnswer(AccountingListener accountingListener) {
+ this.accountingListener = accountingListener;
+ }
+
@Override
public Void answer(final InvocationOnMock invocation) throws Throwable {
final Ledger ledger = invocation.getArgumentAt(0, Ledger.class);
makeLedgerResponsive(ledger, (LedgerManager) invocation.getMock());
+ accountingListener.onPostLedger(TenantContextHolder.checkedGetIdentifier(), ledger.getIdentifier());
return null;
}
}
@@ -512,7 +521,7 @@
}
}
- static void mockAccountingPrereqs(final LedgerManager ledgerManagerMock) {
+ static void mockAccountingPrereqs(final LedgerManager ledgerManagerMock, final AccountingListener accountingListener) {
makeAccountResponsive(loanFundsSourceAccount(), universalCreationDate, ledgerManagerMock);
makeAccountResponsive(loanOriginationFeesIncomeAccount(), universalCreationDate, ledgerManagerMock);
makeAccountResponsive(processingFeeIncomeAccount(), universalCreationDate, ledgerManagerMock);
@@ -536,7 +545,7 @@
Mockito.doAnswer(new FindAccountAnswer()).when(ledgerManagerMock).findAccount(Matchers.anyString());
Mockito.doAnswer(new CreateAccountAnswer()).when(ledgerManagerMock).createAccount(Matchers.any());
Mockito.doAnswer(new CreateJournalEntryAnswer()).when(ledgerManagerMock).createJournalEntry(Matchers.any(JournalEntry.class));
- Mockito.doAnswer(new CreateLedgerAnswer()).when(ledgerManagerMock).createLedger(Matchers.any(Ledger.class));
+ Mockito.doAnswer(new CreateLedgerAnswer(accountingListener)).when(ledgerManagerMock).createLedger(Matchers.any(Ledger.class));
}
static void mockBalance(final String accountIdentifier, final BigDecimal balance) {
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
index 5caf2ea..c42f31f 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
@@ -152,12 +152,26 @@
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
+ static class InterruptedInALambdaException extends RuntimeException {
+
+ private final InterruptedException interruptedException;
+
+ InterruptedInALambdaException(InterruptedException e) {
+ interruptedException = e;
+ }
+
+ void throwWrappedException() throws InterruptedException {
+ throw interruptedException;
+ }
+ }
+
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(
selectorName = EventConstants.SELECTOR_NAME,
selectorValue = IndividualLoanEventConstants.APPROVE_INDIVIDUALLOAN_CASE)
- public IndividualLoanCommandEvent process(final ApproveCommand command) {
+ public IndividualLoanCommandEvent process(final ApproveCommand command) throws InterruptedException
+ {
final String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
@@ -170,14 +184,25 @@
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
//Create the needed account assignments for groups and persist them for the case.
- designatorToAccountIdentifierMapper.getGroupsNeedingLedgers()
- .map(groupNeedingLedger -> new AccountAssignment(groupNeedingLedger.getGroupName(),
- accountingAdapter.createLedger(
- dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
- groupNeedingLedger.getGroupName(),
- groupNeedingLedger.getParentLedger())))
- .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
- .forEach(caseAccountAssignmentEntity -> dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity));
+ try {
+ designatorToAccountIdentifierMapper.getGroupsNeedingLedgers()
+ .map(groupNeedingLedger -> {
+ try {
+ final String createdLedgerIdentifier = accountingAdapter.createLedger(
+ dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
+ groupNeedingLedger.getGroupName(),
+ groupNeedingLedger.getParentLedger());
+ return new AccountAssignment(groupNeedingLedger.getGroupName(), createdLedgerIdentifier);
+ } catch (InterruptedException e) {
+ throw new InterruptedInALambdaException(e);
+ }
+ })
+ .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
+ .forEach(caseAccountAssignmentEntity -> dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity));
+ }
+ catch (final InterruptedInALambdaException e) {
+ e.throwWrappedException();
+ }
//Create the needed account assignments and persist them for the case.
designatorToAccountIdentifierMapper.getLedgersNeedingAccounts()
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
index d305e43..217f1eb 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
@@ -24,6 +24,7 @@
import io.mifos.core.lang.DateConverter;
import io.mifos.core.lang.DateRange;
import io.mifos.core.lang.ServiceException;
+import io.mifos.core.lang.listening.EventExpectation;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import io.mifos.portfolio.service.ServiceConstants;
@@ -39,6 +40,7 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -54,12 +56,15 @@
public enum IdentifierType {LEDGER, ACCOUNT}
private final LedgerManager ledgerManager;
+ private final AccountingListener accountingListener;
private final Logger logger;
@Autowired
public AccountingAdapter(@SuppressWarnings("SpringJavaAutowiringInspection") final LedgerManager ledgerManager,
+ final AccountingListener accountingListener,
@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
this.ledgerManager = ledgerManager;
+ this.accountingListener = accountingListener;
this.logger = logger;
}
@@ -170,7 +175,12 @@
throw ServiceException.internalError("Could not find the account with identifier ''{0}''", accountIdentifier);
}
}
- public String createLedger(final String customerIdentifier, final String groupName, final String parentLedger) {
+
+ public String createLedger(
+ final String customerIdentifier,
+ final String groupName,
+ final String parentLedger) throws InterruptedException
+ {
final Ledger ledger = ledgerManager.findLedger(parentLedger);
final List<Ledger> subLedgers = ledger.getSubLedgers() == null ? Collections.emptyList() : ledger.getSubLedgers();
@@ -185,7 +195,13 @@
logger.info("Creating ledger with identifier '{}'", ledgerIdentifer);
- ledgerManager.createLedger(generatedLedger); //TODO: wait?
+ final EventExpectation expectation = accountingListener.expectLedgerCreation(generatedLedger.getIdentifier());
+ ledgerManager.createLedger(generatedLedger);
+ final boolean ledgerCreationDetected = expectation.waitForOccurrence(5, TimeUnit.SECONDS);
+ if (!ledgerCreationDetected)
+ logger.warn("Waited 5 seconds for creation of ledger '{}', but it was not detected. This could cause subsequent " +
+ "account creations to fail. Is there something wrong with the accounting service? Is ActiveMQ setup properly?",
+ generatedLedger.getIdentifier());
return ledgerIdentifer;
}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingListener.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingListener.java
new file mode 100644
index 0000000..034510a
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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 io.mifos.portfolio.service.internal.util;
+
+import io.mifos.accounting.api.v1.EventConstants;
+import io.mifos.core.lang.TenantContextHolder;
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.lang.listening.EventExpectation;
+import io.mifos.core.lang.listening.EventKey;
+import io.mifos.core.lang.listening.TenantedEventListener;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class AccountingListener {
+ private final TenantedEventListener eventListener = new TenantedEventListener();
+
+ @JmsListener(
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_POST_LEDGER,
+ subscription = EventConstants.DESTINATION
+ )
+ public void onPostLedger(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventListener.notify(new EventKey(tenant, EventConstants.POST_LEDGER, payload));
+ }
+
+
+ EventExpectation expectLedgerCreation(final String ledgerIdentifier) {
+ return eventListener.expect(new EventKey(TenantContextHolder.checkedGetIdentifier(), EventConstants.POST_LEDGER, ledgerIdentifier));
+ }
+}
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/PaymentBuilderTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/PaymentBuilderTest.java
index b113d50..b98742b 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/PaymentBuilderTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/PaymentBuilderTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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 io.mifos.individuallending.internal.service;
import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;