This is an automated email from the ASF dual-hosted git repository. arnold pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit f46abaf9e288f88d85f3959018cf2baa30d252cd Author: Arnold Galovics <[email protected]> AuthorDate: Thu Sep 1 17:57:59 2022 +0200 FINERACT-1694: External event storage integration --- build.gradle | 3 +- .../fixeddeposit/v1/FixedDepositAccountDataV1.avsc | 3 + .../src/main/avro/generic/v1/CalendarDataV1.avsc | 2 + .../src/main/avro/group/v1/GroupGeneralDataV1.avsc | 2 + .../main/avro/loan/v1/DelinquencyBucketDataV1.avsc | 1 + .../src/main/avro/loan/v1/LoanAccountDataV1.avsc | 7 ++ .../src/main/avro/loan/v1/LoanChargeDataV1.avsc | 1 + .../src/main/avro/loan/v1/LoanProductDataV1.avsc | 5 + .../main/avro/loan/v1/LoanTransactionDataV1.avsc | 1 + .../src/main/avro/office/v1/OfficeDataV1.avsc | 1 + .../v1/RecurringDepositAccountDataV1.avsc | 2 + .../main/avro/savings/v1/SavingsAccountDataV1.avsc | 3 + .../v1/SavingsAccountTransactionDataV1.avsc | 1 + .../src/main/avro/share/v1/ShareAccountDataV1.avsc | 2 + .../src/main/avro/share/v1/ShareProductDataV1.avsc | 2 + .../external/repository/domain/ExternalEvent.java | 2 +- .../service/DelayedExternalEventService.java | 8 ++ .../BusinessEventSerializerComparator.java | 43 -------- .../BusinessEventSerializerFactory.java | 6 +- .../SavingsAccountTransactionDataMapper.java | 29 ++++++ ...anAdjustTransactionBusinessEventSerializer.java | 13 ++- ...sAccountTransactionBusinessEventSerializer.java | 58 +++++++++++ .../service/LoanReadPlatformServiceImpl.java | 11 +- .../portfolio/savings/data/SavingsAccountData.java | 1 - .../0045_external_event_table_data_binary.xml | 5 +- .../service/DelayedExternalEventServiceTest.java | 116 +++++++++++++++++++++ .../external/service/ExternalEventServiceTest.java | 115 ++++++++++++++++++++ .../src/test/resources/application-test.properties | 1 + integration-tests/build.gradle | 2 +- 29 files changed, 386 insertions(+), 60 deletions(-) diff --git a/build.gradle b/build.gradle index 7fe39014a..68b87f05f 100644 --- a/build.gradle +++ b/build.gradle @@ -279,6 +279,8 @@ configure(project.fineractJavaProjects) { sourceSets.main.output.resourcesDir = sourceSets.main.java.outputDir sourceSets.test.output.resourcesDir = sourceSets.test.java.outputDir + check.dependsOn('cucumber') + configurations { implementation.setCanBeResolved(true) api.setCanBeResolved(true) @@ -536,7 +538,6 @@ configure(project.fineractJavaProjects) { test { useJUnitPlatform() - dependsOn 'cucumber' if (project.hasProperty('excludeTests')) { filter { diff --git a/fineract-avro-schemas/src/main/avro/fixeddeposit/v1/FixedDepositAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/fixeddeposit/v1/FixedDepositAccountDataV1.avsc index 09971d770..ea91d36ee 100644 --- a/fineract-avro-schemas/src/main/avro/fixeddeposit/v1/FixedDepositAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/fixeddeposit/v1/FixedDepositAccountDataV1.avsc @@ -84,6 +84,7 @@ ] }, { + "default": null, "name": "fieldOfficerName", "type": [ "null", @@ -219,6 +220,7 @@ ] }, { + "default": null, "name": "transactions", "type": [ "null", @@ -229,6 +231,7 @@ ] }, { + "default": null, "name": "charges", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/generic/v1/CalendarDataV1.avsc b/fineract-avro-schemas/src/main/avro/generic/v1/CalendarDataV1.avsc index 9aa8df4b4..388f482ee 100644 --- a/fineract-avro-schemas/src/main/avro/generic/v1/CalendarDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/generic/v1/CalendarDataV1.avsc @@ -172,6 +172,7 @@ ] }, { + "default": null, "name": "recurringDates", "type": [ "null", @@ -182,6 +183,7 @@ ] }, { + "default": null, "name": "nextTenRecurringDates", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/group/v1/GroupGeneralDataV1.avsc b/fineract-avro-schemas/src/main/avro/group/v1/GroupGeneralDataV1.avsc index 2f51475b0..4f6094a2c 100644 --- a/fineract-avro-schemas/src/main/avro/group/v1/GroupGeneralDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/group/v1/GroupGeneralDataV1.avsc @@ -124,6 +124,7 @@ ] }, { + "default": null, "name": "groupRoles", "type": [ "null", @@ -134,6 +135,7 @@ ] }, { + "default": null, "name": "calendarsData", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/DelinquencyBucketDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/DelinquencyBucketDataV1.avsc index 20dae32c3..2a60d7088 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/DelinquencyBucketDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/DelinquencyBucketDataV1.avsc @@ -20,6 +20,7 @@ ] }, { + "default": null, "name": "ranges", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc index 3ea653e6f..a3b18cf00 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc @@ -436,6 +436,7 @@ ] }, { + "default": null, "name": "transactions", "type": [ "null", @@ -446,6 +447,7 @@ ] }, { + "default": null, "name": "charges", "type": [ "null", @@ -464,6 +466,7 @@ ] }, { + "default": null, "name": "disbursementDetails", "type": [ "null", @@ -514,6 +517,7 @@ ] }, { + "default": null, "name": "accountLinkingOptions", "type": [ "null", @@ -564,6 +568,7 @@ ] }, { + "default": null, "name": "emiAmountVariations", "type": [ "null", @@ -574,6 +579,7 @@ ] }, { + "default": null, "name": "clientActiveLoanOptions", "type": [ "null", @@ -640,6 +646,7 @@ ] }, { + "default": null, "name": "overdueCharges", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc index 6e0f6cb7c..a11601ef7 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc @@ -124,6 +124,7 @@ ] }, { + "default": null, "name": "chargeOptions", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductDataV1.avsc index 3e6770fa8..8576f4c20 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductDataV1.avsc @@ -460,6 +460,7 @@ ] }, { + "default": null, "name": "charges", "type": [ "null", @@ -470,6 +471,7 @@ ] }, { + "default": null, "name": "principalVariationsForBorrowerCycle", "type": [ "null", @@ -480,6 +482,7 @@ ] }, { + "default": null, "name": "interestRateVariationsForBorrowerCycle", "type": [ "null", @@ -490,6 +493,7 @@ ] }, { + "default": null, "name": "numberOfRepaymentVariationsForBorrowerCycle", "type": [ "null", @@ -508,6 +512,7 @@ ] }, { + "default": null, "name": "rates", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc index 216d75b6e..1ea5c2a4a 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc @@ -180,6 +180,7 @@ ] }, { + "default": null, "name": "loanChargePaidByList", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/office/v1/OfficeDataV1.avsc b/fineract-avro-schemas/src/main/avro/office/v1/OfficeDataV1.avsc index b93be4215..d7117c21e 100644 --- a/fineract-avro-schemas/src/main/avro/office/v1/OfficeDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/office/v1/OfficeDataV1.avsc @@ -68,6 +68,7 @@ ] }, { + "default": null, "name": "allowedParents", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/recurringdeposit/v1/RecurringDepositAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/recurringdeposit/v1/RecurringDepositAccountDataV1.avsc index 1c4b2b4e4..11cf3b264 100644 --- a/fineract-avro-schemas/src/main/avro/recurringdeposit/v1/RecurringDepositAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/recurringdeposit/v1/RecurringDepositAccountDataV1.avsc @@ -220,6 +220,7 @@ ] }, { + "default": null, "name": "transactions", "type": [ "null", @@ -230,6 +231,7 @@ ] }, { + "default": null, "name": "charges", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountDataV1.avsc index 62f68c180..558f13764 100644 --- a/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountDataV1.avsc @@ -92,6 +92,7 @@ ] }, { + "default": null, "name": "fieldOfficerName", "type": [ "null", @@ -339,6 +340,7 @@ ] }, { + "default": null, "name": "transactions", "type": [ "null", @@ -349,6 +351,7 @@ ] }, { + "default": null, "name": "charges", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountTransactionDataV1.avsc b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountTransactionDataV1.avsc index 7385ddc89..e2cbbe34f 100644 --- a/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountTransactionDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountTransactionDataV1.avsc @@ -180,6 +180,7 @@ ] }, { + "default": null, "name": "chargesPaidByData", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/share/v1/ShareAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/share/v1/ShareAccountDataV1.avsc index 9dcb79362..7f998e596 100644 --- a/fineract-avro-schemas/src/main/avro/share/v1/ShareAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/share/v1/ShareAccountDataV1.avsc @@ -108,6 +108,7 @@ ] }, { + "default": null, "name": "purchasedShares", "type": [ "null", @@ -118,6 +119,7 @@ ] }, { + "default": null, "name": "savingsAccountId", "type": [ "null", diff --git a/fineract-avro-schemas/src/main/avro/share/v1/ShareProductDataV1.avsc b/fineract-avro-schemas/src/main/avro/share/v1/ShareProductDataV1.avsc index 1aa53bc27..6ddf1e8da 100644 --- a/fineract-avro-schemas/src/main/avro/share/v1/ShareProductDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/share/v1/ShareProductDataV1.avsc @@ -108,6 +108,7 @@ ] }, { + "default": null, "name": "marketPrice", "type": [ "null", @@ -118,6 +119,7 @@ ] }, { + "default": null, "name": "charges", "type": [ "null", diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java index 73933d6cb..961d51cbc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEvent.java @@ -54,7 +54,7 @@ public class ExternalEvent extends AbstractPersistableCustom { @Setter private ExternalEventStatus status; - @Column(name = "sent_at", nullable = false) + @Column(name = "sent_at", nullable = true) @Setter private OffsetDateTime sentAt; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventService.java index b415a0010..f043a9f84 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventService.java @@ -45,6 +45,14 @@ public class DelayedExternalEventService { return !localEventStorage.get().isEmpty(); } + public void clearEnqueuedEvents() { + localEventStorage.get().clear(); + } + + public List<BusinessEvent<?>> getEnqueuedEvents() { + return List.copyOf(localEventStorage.get()); + } + public void postEnqueuedEvents() { List<BusinessEvent<?>> enqueuedEvents = localEventStorage.get(); if (enqueuedEvents.isEmpty()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/BusinessEventSerializerComparator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/BusinessEventSerializerComparator.java deleted file mode 100644 index 6cff1b1d5..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/BusinessEventSerializerComparator.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 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.event.external.service.serialization; - -import java.util.Comparator; -import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; - -public class BusinessEventSerializerComparator implements Comparator<BusinessEventSerializer> { - - @Override - public int compare(BusinessEventSerializer o1, BusinessEventSerializer o2) { - int o1Order = getOrderOrDefault(o1); - int o2Order = getOrderOrDefault(o2); - return Integer.compare(o1Order, o2Order); - } - - private int getOrderOrDefault(BusinessEventSerializer serializer) { - Order orderAnnotation = serializer.getClass().getAnnotation(Order.class); - if (orderAnnotation != null) { - return orderAnnotation.value(); - } else { - return Ordered.LOWEST_PRECEDENCE; - } - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/BusinessEventSerializerFactory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/BusinessEventSerializerFactory.java index 86421db0d..7046a56bd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/BusinessEventSerializerFactory.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/BusinessEventSerializerFactory.java @@ -19,19 +19,17 @@ package org.apache.fineract.infrastructure.event.external.service.serialization; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer; import org.apache.fineract.portfolio.businessevent.domain.BusinessEvent; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class BusinessEventSerializerFactory { private final List<BusinessEventSerializer> serializers; - public BusinessEventSerializerFactory(List<BusinessEventSerializer> serializers) { - this.serializers = serializers.stream().sorted(new BusinessEventSerializerComparator()).toList(); - } - public <T> BusinessEventSerializer create(BusinessEvent<T> event) { for (BusinessEventSerializer serializer : serializers) { if (serializer.canSerialize(event)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/savings/SavingsAccountTransactionDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/savings/SavingsAccountTransactionDataMapper.java new file mode 100644 index 000000000..dabf8bc7e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/savings/SavingsAccountTransactionDataMapper.java @@ -0,0 +1,29 @@ +/** + * 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.event.external.service.serialization.mapper.savings; + +import org.apache.fineract.avro.savings.v1.SavingsAccountTransactionDataV1; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface SavingsAccountTransactionDataMapper { + + SavingsAccountTransactionDataV1 map(SavingsAccountTransactionData source); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializer.java index 62d163d7a..179b8c555 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializer.java @@ -50,13 +50,18 @@ public class LoanAdjustTransactionBusinessEventSerializer implements BusinessEve public <T> byte[] serialize(BusinessEvent<T> rawEvent) throws IOException { LoanAdjustTransactionBusinessEvent event = (LoanAdjustTransactionBusinessEvent) rawEvent; LoanTransaction transactionToAdjust = event.get().getTransactionToAdjust(); - LoanTransaction newTransactionDetail = event.get().getNewTransactionDetail(); LoanTransactionData transactionToAdjustData = service.retrieveLoanTransaction(transactionToAdjust.getLoan().getId(), transactionToAdjust.getId()); - LoanTransactionData newTransactionDetailData = service.retrieveLoanTransaction(newTransactionDetail.getLoan().getId(), - newTransactionDetail.getId()); LoanTransactionDataV1 transactionToAdjustAvroDto = mapper.map(transactionToAdjustData); - LoanTransactionDataV1 newTransactionDetailAvroDto = mapper.map(newTransactionDetailData); + + LoanTransaction newTransactionDetail = event.get().getNewTransactionDetail(); + LoanTransactionDataV1 newTransactionDetailAvroDto = null; + if (newTransactionDetail != null) { + LoanTransactionData newTransactionDetailData = service.retrieveLoanTransaction(newTransactionDetail.getLoan().getId(), + newTransactionDetail.getId()); + newTransactionDetailAvroDto = mapper.map(newTransactionDetailData); + + } LoanTransactionAdjustmentDataV1 avroDto = new LoanTransactionAdjustmentDataV1(transactionToAdjustAvroDto, newTransactionDetailAvroDto); ByteBuffer buffer = avroDto.toByteBuffer(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/savings/SavingsAccountTransactionBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/savings/SavingsAccountTransactionBusinessEventSerializer.java new file mode 100644 index 000000000..487c3d6dd --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/savings/SavingsAccountTransactionBusinessEventSerializer.java @@ -0,0 +1,58 @@ +/** + * 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.event.external.service.serialization.serializer.savings; + +import java.io.IOException; +import java.nio.ByteBuffer; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.avro.savings.v1.SavingsAccountTransactionDataV1; +import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.savings.SavingsAccountTransactionDataMapper; +import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer; +import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter; +import org.apache.fineract.portfolio.businessevent.domain.BusinessEvent; +import org.apache.fineract.portfolio.businessevent.domain.savings.transaction.SavingsAccountTransactionBusinessEvent; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; +import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class SavingsAccountTransactionBusinessEventSerializer implements BusinessEventSerializer { + + private final SavingsAccountReadPlatformService service; + private final SavingsAccountTransactionDataMapper mapper; + private final ByteBufferConverter byteBufferConverter; + + @Override + public <T> boolean canSerialize(BusinessEvent<T> event) { + return event instanceof SavingsAccountTransactionBusinessEvent; + } + + @Override + public <T> byte[] serialize(BusinessEvent<T> rawEvent) throws IOException { + SavingsAccountTransactionBusinessEvent event = (SavingsAccountTransactionBusinessEvent) rawEvent; + SavingsAccountTransaction tx = event.get(); + SavingsAccountTransactionData data = service.retrieveSavingsTransaction(tx.getSavingsAccount().getId(), tx.getId(), + tx.getSavingsAccount().depositAccountType()); + SavingsAccountTransactionDataV1 avroDto = mapper.map(data); + ByteBuffer buffer = avroDto.toByteBuffer(); + return byteBufferConverter.convert(buffer); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 11fb67d8d..2eab5338b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -212,8 +212,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { public LoanAccountData retrieveOne(final Long loanId) { try { - final AppUser currentUser = this.context.authenticatedUser(); - final String hierarchy = currentUser.getOffice().getHierarchy(); + final String hierarchy = getHierarchyString(); final String hierarchySearchString = hierarchy + "%"; final LoanMapper rm = new LoanMapper(sqlGenerator, delinquencyReadPlatformService); @@ -231,6 +230,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } + private String getHierarchyString() { + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.getAuthenticatedUserIfPresent(); + } + return Optional.ofNullable(currentUser).map(appUser -> appUser.getOffice().getHierarchy()).orElse("."); + } + @Override public LoanAccountData retrieveLoanByLoanAccount(String loanAccountNumber) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java index 5eed082b8..0b006421d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java @@ -406,7 +406,6 @@ public final class SavingsAccountData implements Serializable { return this.interestCompoundingPeriodType; } - public Integer getInterestCompoundingPeriodTypeId() { return this.interestCompoundingPeriodType.getId().intValue(); } diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0045_external_event_table_data_binary.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0045_external_event_table_data_binary.xml index 65a5d3629..56cdc032d 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0045_external_event_table_data_binary.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0045_external_event_table_data_binary.xml @@ -24,17 +24,16 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd"> <changeSet author="fineract" id="1"> <delete tableName="m_external_event"/> + <dropColumn tableName="m_external_event" columnName="data"/> </changeSet> <changeSet author="fineract" id="2-mysql" context="mysql"> - <dropColumn tableName="m_external_event" columnName="data"/> <addColumn tableName="m_external_event"> - <column name="data" type="VARBINARY"> + <column name="data" type="BLOB"> <constraints nullable="false"/> </column> </addColumn> </changeSet> <changeSet author="fineract" id="2-postgresql" context="postgresql"> - <dropColumn tableName="m_external_event" columnName="data"/> <addColumn tableName="m_external_event"> <column name="data" type="BYTEA"> <constraints nullable="false"/> diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventServiceTest.java new file mode 100644 index 000000000..6f05a7a6a --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventServiceTest.java @@ -0,0 +1,116 @@ +/** + * 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.event.external.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.List; +import org.apache.fineract.portfolio.businessevent.domain.BulkBusinessEvent; +import org.apache.fineract.portfolio.businessevent.domain.BusinessEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({ "rawtypes", "unchecked" }) +class DelayedExternalEventServiceTest { + + @Mock + private ExternalEventService delegate; + + @InjectMocks + private DelayedExternalEventService underTest; + + @BeforeEach + public void setUp() { + underTest.clearEnqueuedEvents(); + } + + @Test + public void testEnqueueEventFailsWhenNullEventIsGiven() { + // given + // when & then + assertThatThrownBy(() -> underTest.enqueueEvent(null)).isExactlyInstanceOf(IllegalArgumentException.class); + } + + @Test + public void testEnqueueEventWorks() { + // given + BusinessEvent event = mock(BusinessEvent.class); + // when + underTest.enqueueEvent(event); + // then + List<BusinessEvent<?>> enqueuedEvents = underTest.getEnqueuedEvents(); + assertThat(enqueuedEvents).hasSize(1); + assertThat(enqueuedEvents.get(0)).isEqualTo(event); + } + + @Test + public void testHasEnqueuedEventsReturnsTrueWhenEventIsEnqueued() { + // given + BusinessEvent event = mock(BusinessEvent.class); + underTest.enqueueEvent(event); + // when + boolean result = underTest.hasEnqueuedEvents(); + // then + assertThat(result).isTrue(); + } + + @Test + public void testHasEnqueuedEventsReturnsFalseWhenNoEventIsEnqueued() { + // given + // when + boolean result = underTest.hasEnqueuedEvents(); + // then + assertThat(result).isFalse(); + } + + @Test + public void testPostEnqueuedEventsFailsWhenNoEventIsEnqueued() { + // given + // when & then + assertThatThrownBy(() -> underTest.postEnqueuedEvents()).isExactlyInstanceOf(IllegalStateException.class); + } + + @Test + public void testPostEnqueuedEventsWorks() { + // given + ArgumentCaptor<BusinessEvent> delegateEventCaptor = ArgumentCaptor.forClass(BusinessEvent.class); + + BusinessEvent event = mock(BusinessEvent.class); + underTest.enqueueEvent(event); + // when + underTest.postEnqueuedEvents(); + // then + verify(delegate).postEvent(delegateEventCaptor.capture()); + BusinessEvent delegateEvent = delegateEventCaptor.getValue(); + assertThat(delegateEvent).isExactlyInstanceOf(BulkBusinessEvent.class); + BulkBusinessEvent bulkDelegateEvent = (BulkBusinessEvent) delegateEvent; + List<BusinessEvent<?>> enqueuedEvents = bulkDelegateEvent.get(); + assertThat(enqueuedEvents).hasSize(1); + assertThat(enqueuedEvents.get(0)).isEqualTo(event); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java new file mode 100644 index 000000000..b65c8c907 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java @@ -0,0 +1,115 @@ +/** + * 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.event.external.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.HashMap; +import java.util.Map; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository; +import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEvent; +import org.apache.fineract.infrastructure.event.external.service.idempotency.ExternalEventIdempotencyKeyGenerator; +import org.apache.fineract.infrastructure.event.external.service.serialization.BusinessEventSerializerFactory; +import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer; +import org.apache.fineract.portfolio.businessevent.domain.BusinessEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({ "rawtypes", "unchecked" }) +class ExternalEventServiceTest { + + @Mock + private ExternalEventRepository repository; + @Mock + private ExternalEventIdempotencyKeyGenerator idempotencyKeyGenerator; + @Mock + private BusinessEventSerializerFactory serializerFactory; + + @InjectMocks + private ExternalEventService underTest; + + @BeforeEach + public void setUp() { + FineractPlatformTenant tenant = new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null); + ThreadLocalContextUtil.setTenant(tenant); + ThreadLocalContextUtil + .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); + } + + @Test + public void testPostEventShouldFailWhenNullEventIsGiven() { + // given + // when & then + assertThatThrownBy(() -> underTest.postEvent(null)).isExactlyInstanceOf(IllegalArgumentException.class); + } + + @Test + public void testPostEventShouldFailWhenEventSerializationFails() throws IOException { + // given + BusinessEvent event = mock(BusinessEvent.class); + BusinessEventSerializer eventSerializer = mock(BusinessEventSerializer.class); + + given(idempotencyKeyGenerator.generate(event)).willReturn(""); + given(serializerFactory.create(event)).willReturn(eventSerializer); + given(eventSerializer.serialize(event)).willThrow(IOException.class); + // when & then + assertThatThrownBy(() -> underTest.postEvent(event)).isExactlyInstanceOf(RuntimeException.class); + } + + @Test + public void testPostEventShouldWork() throws IOException { + // given + ArgumentCaptor<ExternalEvent> externalEventArgumentCaptor = ArgumentCaptor.forClass(ExternalEvent.class); + + String eventType = "TestType"; + String idempotencyKey = "key"; + BusinessEvent event = mock(BusinessEvent.class); + BusinessEventSerializer eventSerializer = mock(BusinessEventSerializer.class); + byte[] data = new byte[0]; + + given(event.getType()).willReturn(eventType); + given(idempotencyKeyGenerator.generate(event)).willReturn(idempotencyKey); + given(serializerFactory.create(event)).willReturn(eventSerializer); + given(eventSerializer.serialize(event)).willReturn(data); + // when + underTest.postEvent(event); + // then + verify(repository).save(externalEventArgumentCaptor.capture()); + ExternalEvent externalEvent = externalEventArgumentCaptor.getValue(); + assertThat(externalEvent.getIdempotencyKey()).isEqualTo(idempotencyKey); + assertThat(externalEvent.getData()).isEqualTo(data); + assertThat(externalEvent.getType()).isEqualTo(eventType); + } +} diff --git a/fineract-provider/src/test/resources/application-test.properties b/fineract-provider/src/test/resources/application-test.properties index 5999ebcc3..425a5d45b 100644 --- a/fineract-provider/src/test/resources/application-test.properties +++ b/fineract-provider/src/test/resources/application-test.properties @@ -45,6 +45,7 @@ fineract.partitioned-job.partitioned-job-properties[0].thread-count=1 fineract.remote-job-message-handler.spring-events.enabled=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED:true} fineract.remote-job-message-handler.jms.enabled=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_ENABLED:false} fineract.remote-job-message-handler.jms.request-queue-name=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_QUEUE_NAME:JMS-request-queue} +fineract.events.external.enabled=${FINERACT_EXTERNAL_EVENTS_ENABLED:false} management.health.jms.enabled=false diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle index 39ee749ae..c256c2495 100644 --- a/integration-tests/build.gradle +++ b/integration-tests/build.gradle @@ -75,7 +75,7 @@ cargo { } else { jvmArgs += '-Dspring.datasource.hikari.driverClassName=org.mariadb.jdbc.Driver -Dspring.datasource.hikari.jdbcUrl=jdbc:mariadb://localhost:3306/fineract_tenants -Dspring.datasource.hikari.username=root -Dspring.datasource.hikari.password=mysql -Dfineract.tenant.host=localhost -Dfineract.tenant.port=3306 -Dfineract.tenant.username=root -Dfineract.tenant.password=mysql' } - jvmArgs += ' -Dspring.profiles.active=test' + jvmArgs += ' -Dspring.profiles.active=test -Dfineract.events.external.enabled=true' property 'cargo.start.jvmargs', jvmArgs property 'cargo.tomcat.connector.keystoreFile', file("$rootDir/fineract-provider/src/main/resources/keystore.jks") property 'cargo.tomcat.connector.keystorePass', 'openmf'
