FINERACT-1694 Send Asynchronous Events
This commit is contained in:
committed by
Arnold Gálovics
parent
744d38c588
commit
e9ae88d057
+17
@@ -134,5 +134,22 @@ public class FineractProperties {
|
|||||||
public static class FineractExternalEventsProperties {
|
public static class FineractExternalEventsProperties {
|
||||||
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
private FineractExternalEventsProducerProperties producer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class FineractExternalEventsProducerProperties {
|
||||||
|
|
||||||
|
private int readBatchSize;
|
||||||
|
private FineractExternalEventsProducerJmsProperties jms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class FineractExternalEventsProducerJmsProperties {
|
||||||
|
|
||||||
|
private boolean enabled;
|
||||||
|
private String eventQueueName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 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.exception;
|
||||||
|
|
||||||
|
public class AcknowledgementTimeoutException extends RuntimeException {
|
||||||
|
|
||||||
|
public AcknowledgementTimeoutException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SendAsynchronousEventsConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private SendAsynchronousEventsTasklet tasklet;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step sendAsynchronousEventsStep() {
|
||||||
|
return steps.get(JobName.SEND_ASYNCHRONOUS_EVENTS.name()).tasklet(tasklet).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job sendAsynchronousEventsJob() {
|
||||||
|
return jobs.get(JobName.SEND_ASYNCHRONOUS_EVENTS.name()).start(sendAsynchronousEventsStep()).incrementer(new RunIdIncrementer())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.avro.MessageV1;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.producer.ExternalEventProducer;
|
||||||
|
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.repository.domain.ExternalEventStatus;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.message.MessageFactory;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class SendAsynchronousEventsTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final FineractProperties fineractProperties;
|
||||||
|
private final ExternalEventRepository repository;
|
||||||
|
private final ExternalEventProducer eventProducer;
|
||||||
|
private final MessageFactory messageFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||||
|
try {
|
||||||
|
List<ExternalEvent> events = getQueuedEventsBatch();
|
||||||
|
processEvents(events);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error occurred while processing events: ", e);
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ExternalEvent> getQueuedEventsBatch() {
|
||||||
|
int readBatchSize = getBatchSize();
|
||||||
|
Pageable batchSize = PageRequest.ofSize(readBatchSize);
|
||||||
|
List<ExternalEvent> events = repository.findByStatusOrderById(ExternalEventStatus.TO_BE_SENT, batchSize);
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processEvents(List<ExternalEvent> queuedEvents) {
|
||||||
|
for (ExternalEvent event : queuedEvents) {
|
||||||
|
MessageV1 message = messageFactory.createMessage(event);
|
||||||
|
eventProducer.sendEvent(message);
|
||||||
|
event.setStatus(ExternalEventStatus.SENT);
|
||||||
|
event.setSentAt(DateUtils.getOffsetDateTimeOfTenant());
|
||||||
|
repository.save(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getBatchSize() {
|
||||||
|
return fineractProperties.getEvents().getExternal().getProducer().getReadBatchSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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.producer;
|
||||||
|
|
||||||
|
import org.apache.fineract.avro.MessageV1;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.exception.AcknowledgementTimeoutException;
|
||||||
|
|
||||||
|
public interface ExternalEventProducer {
|
||||||
|
|
||||||
|
void sendEvent(MessageV1 message) throws AcknowledgementTimeoutException;
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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.producer;
|
||||||
|
|
||||||
|
import org.apache.fineract.avro.MessageV1;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.exception.AcknowledgementTimeoutException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ExternalEventProducerImpl implements ExternalEventProducer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendEvent(MessageV1 message) throws AcknowledgementTimeoutException {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-1
@@ -18,7 +18,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.event.external.repository;
|
package org.apache.fineract.infrastructure.event.external.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEvent;
|
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventStatus;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
public interface ExternalEventRepository extends JpaRepository<ExternalEvent, Long> {}
|
public interface ExternalEventRepository extends JpaRepository<ExternalEvent, Long> {
|
||||||
|
|
||||||
|
List<ExternalEvent> findByStatusOrderById(ExternalEventStatus status, Pageable batchSize);
|
||||||
|
}
|
||||||
|
|||||||
+30
-1
@@ -21,10 +21,14 @@ package org.apache.fineract.infrastructure.event.external.service.message;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.avro.BulkMessageV1;
|
import org.apache.fineract.avro.BulkMessageV1;
|
||||||
import org.apache.fineract.avro.MessageV1;
|
import org.apache.fineract.avro.MessageV1;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEvent;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.message.domain.BulkMessageData;
|
import org.apache.fineract.infrastructure.event.external.service.message.domain.BulkMessageData;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageCategory;
|
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageCategory;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageData;
|
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageData;
|
||||||
@@ -33,10 +37,17 @@ import org.apache.fineract.infrastructure.event.external.service.message.domain.
|
|||||||
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageIdempotencyKey;
|
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageIdempotencyKey;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageSource;
|
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageSource;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageType;
|
import org.apache.fineract.infrastructure.event.external.service.message.domain.MessageType;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class MessageFactory {
|
@RequiredArgsConstructor
|
||||||
|
public class MessageFactory implements InitializingBean {
|
||||||
|
|
||||||
|
private final ByteBufferConverter byteBufferConverter;
|
||||||
|
private static final String SOURCE_UUID = UUID.randomUUID().toString();
|
||||||
|
|
||||||
public MessageV1 createMessage(MessageId id, MessageSource source, MessageType type, MessageCategory category,
|
public MessageV1 createMessage(MessageId id, MessageSource source, MessageType type, MessageCategory category,
|
||||||
MessageIdempotencyKey idempotencyKey, MessageDataSchema dataSchema, MessageData data) {
|
MessageIdempotencyKey idempotencyKey, MessageDataSchema dataSchema, MessageData data) {
|
||||||
@@ -65,6 +76,19 @@ public class MessageFactory {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MessageV1 createMessage(ExternalEvent event) {
|
||||||
|
MessageId id = new MessageId(event.getId().intValue());
|
||||||
|
MessageSource source = new MessageSource(SOURCE_UUID);
|
||||||
|
MessageType type = new MessageType(event.getType());
|
||||||
|
String messageCategory = "nocategory";
|
||||||
|
MessageCategory category = new MessageCategory(messageCategory);
|
||||||
|
MessageIdempotencyKey idempotencyKey = new MessageIdempotencyKey(event.getIdempotencyKey());
|
||||||
|
MessageDataSchema dataSchema = new MessageDataSchema(event.getSchema());
|
||||||
|
MessageData data = new MessageData(byteBufferConverter.convert(event.getData()));
|
||||||
|
MessageV1 message = createMessage(id, source, type, category, idempotencyKey, dataSchema, data);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
private String getTenantId() {
|
private String getTenantId() {
|
||||||
return ThreadLocalContextUtil.getTenant().getName();
|
return ThreadLocalContextUtil.getTenant().getName();
|
||||||
}
|
}
|
||||||
@@ -73,4 +97,9 @@ public class MessageFactory {
|
|||||||
OffsetDateTime createdAt = DateUtils.getOffsetDateTimeOfTenant();
|
OffsetDateTime createdAt = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
return createdAt.withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
return createdAt.withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
log.info("Message source set to {}", SOURCE_UUID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -56,7 +56,8 @@ public enum JobName {
|
|||||||
INCREASE_BUSINESS_DATE_BY_1_DAY("Increase Business Date by 1 day"), //
|
INCREASE_BUSINESS_DATE_BY_1_DAY("Increase Business Date by 1 day"), //
|
||||||
INCREASE_COB_DATE_BY_1_DAY("Increase COB Date by 1 day"), //
|
INCREASE_COB_DATE_BY_1_DAY("Increase COB Date by 1 day"), //
|
||||||
LOAN_COB("Loan COB"), //
|
LOAN_COB("Loan COB"), //
|
||||||
LOAN_DELINQUENCY_CLASSIFICATION("Loan Delinquency Classification");
|
LOAN_DELINQUENCY_CLASSIFICATION("Loan Delinquency Classification"), //
|
||||||
|
SEND_ASYNCHRONOUS_EVENTS("Send Asynchronous Events");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ fineract.remote-job-message-handler.jms.request-queue-name=${FINERACT_REMOTE_JOB
|
|||||||
fineract.remote-job-message-handler.jms.broker-url=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_BROKER_URL:tcp://127.0.0.1:61616}
|
fineract.remote-job-message-handler.jms.broker-url=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_BROKER_URL:tcp://127.0.0.1:61616}
|
||||||
|
|
||||||
fineract.events.external.enabled=${FINERACT_EXTERNAL_EVENTS_ENABLED:false}
|
fineract.events.external.enabled=${FINERACT_EXTERNAL_EVENTS_ENABLED:false}
|
||||||
|
fineract.events.external.producer.read-batch-size=${FINERACT_EXTERNAL_EVENTS_PRODUCER_READ_BATCH_SIZE:1000}
|
||||||
|
fineract.events.external.producer.jms.enabled=${FINERACT_EXTERNAL_EVENTS_PRODUCER_JMS_ENABLED:false}
|
||||||
|
fineract.events.external.producer.jms.event-queue-name=${FINERACT_EXTERNAL_EVENTS_PRODUCER_JMS_QUEUE_NAME:JMS-event-queue}
|
||||||
|
|
||||||
|
|
||||||
# Logging pattern for the console
|
# Logging pattern for the console
|
||||||
logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}
|
logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}
|
||||||
|
|||||||
@@ -68,4 +68,5 @@
|
|||||||
<include file="parts/0046_external_event_table_schema_info.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0046_external_event_table_schema_info.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0047_add_loan_delinquency_tags_business_step.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0047_add_loan_delinquency_tags_business_step.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0048_rework_loan_account_locks.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0048_rework_loan_account_locks.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0049_add_send_asynchronous_events_job.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||||
|
<changeSet author="fineract" id="send_async_events_job">
|
||||||
|
<insert tableName="job">
|
||||||
|
<column name="name" value="Send Asynchronous Events"/>
|
||||||
|
<column name="display_name" value="Send Asynchronous Events"/>
|
||||||
|
<column name="cron_expression" value="0 0/1 * * * ?"/>
|
||||||
|
<column name="create_time" valueDate="${current_datetime}"/>
|
||||||
|
<column name="task_priority" valueNumeric="5"/>
|
||||||
|
<column name="group_name"/>
|
||||||
|
<column name="previous_run_start_time"/>
|
||||||
|
<column name="job_key" value="Send Asynchronous Events _ DEFAULT"/>
|
||||||
|
<column name="initializing_errorlog"/>
|
||||||
|
<column name="is_active" valueBoolean="true"/>
|
||||||
|
<column name="currently_running" valueBoolean="false"/>
|
||||||
|
<column name="updates_allowed" valueBoolean="true"/>
|
||||||
|
<column name="scheduler_group" valueNumeric="0"/>
|
||||||
|
<column name="is_misfired" valueBoolean="false"/>
|
||||||
|
<column name="node_id" valueNumeric="1"/>
|
||||||
|
<column name="is_mismatched_job" valueBoolean="true"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.fineract.avro.MessageV1;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.exception.AcknowledgementTimeoutException;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.producer.ExternalEventProducer;
|
||||||
|
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.repository.domain.ExternalEventStatus;
|
||||||
|
import org.apache.fineract.infrastructure.event.external.service.message.MessageFactory;
|
||||||
|
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.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class SendAsynchronousEventsTaskletTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FineractProperties fineractProperties;
|
||||||
|
@Mock
|
||||||
|
private ExternalEventRepository repository;
|
||||||
|
@Mock
|
||||||
|
private ExternalEventProducer eventProducer;
|
||||||
|
@Mock
|
||||||
|
private MessageFactory messageFactory;
|
||||||
|
@Mock
|
||||||
|
private StepContribution stepContribution;
|
||||||
|
@Mock
|
||||||
|
private ChunkContext chunkContext;
|
||||||
|
private SendAsynchronousEventsTasklet underTest;
|
||||||
|
private RepeatStatus resultStatus;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||||
|
ThreadLocalContextUtil
|
||||||
|
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
|
||||||
|
configureExternalEventsProducerReadBatchSizeProperty(1000);
|
||||||
|
underTest = new SendAsynchronousEventsTasklet(fineractProperties, repository, eventProducer, messageFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureExternalEventsProducerReadBatchSizeProperty(int readBatchSize) {
|
||||||
|
FineractProperties.FineractEventsProperties eventsProperties = new FineractProperties.FineractEventsProperties();
|
||||||
|
FineractProperties.FineractExternalEventsProperties externalProperties = new FineractProperties.FineractExternalEventsProperties();
|
||||||
|
FineractProperties.FineractExternalEventsProducerProperties externalEventsProducerProperties = new FineractProperties.FineractExternalEventsProducerProperties();
|
||||||
|
externalProperties.setEnabled(true);
|
||||||
|
externalEventsProducerProperties.setReadBatchSize(readBatchSize);
|
||||||
|
externalProperties.setProducer(externalEventsProducerProperties);
|
||||||
|
eventsProperties.setExternal(externalProperties);
|
||||||
|
when(fineractProperties.getEvents()).thenReturn(eventsProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenBatchSize2WhenTaskExecutionThenSend2Events() throws Exception {
|
||||||
|
// given
|
||||||
|
List<ExternalEvent> events = Arrays.asList(new ExternalEvent("aType", "aSchema", new byte[0], "aIdemtpotencyKey"),
|
||||||
|
new ExternalEvent("aType", "aSchema", new byte[0], "aIdemtpotencyKey"));
|
||||||
|
when(repository.findByStatusOrderById(Mockito.any(), Mockito.any())).thenReturn(events);
|
||||||
|
when(messageFactory.createMessage(Mockito.any())).thenReturn(new MessageV1());
|
||||||
|
doNothing().when(eventProducer).sendEvent(Mockito.any(MessageV1.class));
|
||||||
|
// when
|
||||||
|
resultStatus = this.underTest.execute(stepContribution, chunkContext);
|
||||||
|
// then
|
||||||
|
verify(eventProducer, times(2)).sendEvent(Mockito.any(MessageV1.class));
|
||||||
|
verify(repository, times(2)).save(Mockito.any(ExternalEvent.class));
|
||||||
|
assertEquals(RepeatStatus.FINISHED, resultStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenBatchSize2WhenEventSendFailsThenExecutionStops() throws Exception {
|
||||||
|
// given
|
||||||
|
List<ExternalEvent> events = Arrays.asList(new ExternalEvent("aType", "aSchema", new byte[0], "aIdemtpotencyKey"),
|
||||||
|
new ExternalEvent("aType", "aSchema", new byte[0], "aIdemtpotencyKey"));
|
||||||
|
when(repository.findByStatusOrderById(Mockito.any(), Mockito.any())).thenReturn(events);
|
||||||
|
when(messageFactory.createMessage(Mockito.any())).thenReturn(new MessageV1());
|
||||||
|
doThrow(new AcknowledgementTimeoutException("Event Send Exception", new RuntimeException())).when(eventProducer)
|
||||||
|
.sendEvent(Mockito.any(MessageV1.class));
|
||||||
|
// when
|
||||||
|
resultStatus = this.underTest.execute(stepContribution, chunkContext);
|
||||||
|
// then
|
||||||
|
verify(repository, times(0)).save(Mockito.any(ExternalEvent.class));
|
||||||
|
assertEquals(RepeatStatus.FINISHED, resultStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenOneEventWhenEventSentThenEventStatusUpdates() throws Exception {
|
||||||
|
// given
|
||||||
|
ArgumentCaptor<ExternalEvent> externalEventArgumentCaptor = ArgumentCaptor.forClass(ExternalEvent.class);
|
||||||
|
List<ExternalEvent> events = Arrays.asList(new ExternalEvent("aType", "aSchema", new byte[0], "aIdemtpotencyKey"));
|
||||||
|
when(repository.findByStatusOrderById(Mockito.any(), Mockito.any())).thenReturn(events);
|
||||||
|
when(messageFactory.createMessage(Mockito.any())).thenReturn(new MessageV1());
|
||||||
|
doNothing().when(eventProducer).sendEvent(Mockito.any(MessageV1.class));
|
||||||
|
// when
|
||||||
|
resultStatus = this.underTest.execute(stepContribution, chunkContext);
|
||||||
|
// then
|
||||||
|
verify(repository).save(externalEventArgumentCaptor.capture());
|
||||||
|
ExternalEvent externalEvent = externalEventArgumentCaptor.getValue();
|
||||||
|
assertThat(externalEvent.getStatus()).isEqualTo(ExternalEventStatus.SENT);
|
||||||
|
assertEquals(RepeatStatus.FINISHED, resultStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -46,6 +46,9 @@ fineract.remote-job-message-handler.spring-events.enabled=${FINERACT_REMOTE_JOB_
|
|||||||
fineract.remote-job-message-handler.jms.enabled=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_ENABLED:false}
|
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.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}
|
fineract.events.external.enabled=${FINERACT_EXTERNAL_EVENTS_ENABLED:false}
|
||||||
|
fineract.events.external.producer.read-batch-size=${FINERACT_EXTERNAL_EVENTS_PRODUCER_READ_BATCH_SIZE:1000}
|
||||||
|
fineract.events.external.producer.jms.enabled=${FINERACT_EXTERNAL_EVENTS_PRODUCER_JMS_ENABLED:false}
|
||||||
|
fineract.events.external.producer.jms.event-queue-name=${FINERACT_EXTERNAL_EVENTS_PRODUCER_JMS_QUEUE_NAME:JMS-event-queue}
|
||||||
|
|
||||||
management.health.jms.enabled=false
|
management.health.jms.enabled=false
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user