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


The following commit(s) were added to refs/heads/develop by this push:
     new 17b36a091 FINERACT-1694: Reworked bulk external events
17b36a091 is described below

commit 17b36a0919896d1b5918993c9a27ebb1839fdef0
Author: Arnold Galovics <[email protected]>
AuthorDate: Sun Sep 4 17:33:45 2022 +0200

    FINERACT-1694: Reworked bulk external events
---
 .../service/BusinessEventNotifierService.java      |   3 +
 .../service/BusinessEventNotifierServiceImpl.java  |  49 ++++++-
 .../service/DelayedExternalEventService.java       |  64 --------
 .../BusinessEventNotifierServiceImplTest.java      | 161 +++++++++++++++++++++
 .../service/DelayedExternalEventServiceTest.java   | 116 ---------------
 5 files changed, 208 insertions(+), 185 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierService.java
index 7c57c9eb7..d666b0eb6 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierService.java
@@ -47,4 +47,7 @@ public interface BusinessEventNotifierService {
      */
     <T extends BusinessEvent<?>> void addPostBusinessEventListener(Class<T> 
eventType, BusinessEventListener<T> listener);
 
+    void startExternalEventRecording();
+
+    void stopExternalEventRecording();
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
index fc8dbf4a0..b0c2ac850 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
@@ -26,6 +26,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
+import 
org.apache.fineract.infrastructure.event.business.domain.BulkBusinessEvent;
 import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
 import 
org.apache.fineract.infrastructure.event.external.service.ExternalEventService;
 import org.springframework.beans.factory.InitializingBean;
@@ -40,6 +41,9 @@ public class BusinessEventNotifierServiceImpl implements 
BusinessEventNotifierSe
     private final Map<Class, List<BusinessEventListener>> preListeners = new 
HashMap<>();
     private final Map<Class, List<BusinessEventListener>> postListeners = new 
HashMap<>();
 
+    private final ThreadLocal<Boolean> eventRecordingEnabled = 
ThreadLocal.withInitial(() -> false);
+    private final ThreadLocal<List<BusinessEvent<?>>> recordedEvents = 
ThreadLocal.withInitial(ArrayList::new);
+
     private final ExternalEventService externalEventService;
     private final FineractProperties fineractProperties;
 
@@ -54,6 +58,7 @@ public class BusinessEventNotifierServiceImpl implements 
BusinessEventNotifierSe
 
     @Override
     public void notifyPreBusinessEvent(BusinessEvent<?> businessEvent) {
+        throwExceptionIfBulkEvent(businessEvent);
         List<BusinessEventListener> businessEventListeners = 
preListeners.get(businessEvent.getClass());
         if (businessEventListeners != null) {
             for (BusinessEventListener eventListener : businessEventListeners) 
{
@@ -74,6 +79,7 @@ public class BusinessEventNotifierServiceImpl implements 
BusinessEventNotifierSe
 
     @Override
     public void notifyPostBusinessEvent(BusinessEvent<?> businessEvent) {
+        throwExceptionIfBulkEvent(businessEvent);
         List<BusinessEventListener> businessEventListeners = 
postListeners.get(businessEvent.getClass());
         if (businessEventListeners != null) {
             for (BusinessEventListener eventListener : businessEventListeners) 
{
@@ -82,14 +88,14 @@ public class BusinessEventNotifierServiceImpl implements 
BusinessEventNotifierSe
         }
         if (isExternalEventPostingEnabled()) {
             // we only want to create external events for operations that were 
successful, hence the post listener
-            externalEventService.postEvent(businessEvent);
+            if (isExternalEventRecordingEnabled()) {
+                recordedEvents.get().add(businessEvent);
+            } else {
+                externalEventService.postEvent(businessEvent);
+            }
         }
     }
 
-    private boolean isExternalEventPostingEnabled() {
-        return fineractProperties.getEvents().getExternal().isEnabled();
-    }
-
     @Override
     public <T extends BusinessEvent<?>> void 
addPostBusinessEventListener(Class<T> eventType, BusinessEventListener<T> 
listener) {
         List<BusinessEventListener> businessEventListeners = 
postListeners.get(eventType);
@@ -99,4 +105,37 @@ public class BusinessEventNotifierServiceImpl implements 
BusinessEventNotifierSe
         }
         businessEventListeners.add(listener);
     }
+
+    private boolean isExternalEventRecordingEnabled() {
+        return eventRecordingEnabled.get();
+    }
+
+    private boolean isExternalEventPostingEnabled() {
+        return fineractProperties.getEvents().getExternal().isEnabled();
+    }
+
+    private void throwExceptionIfBulkEvent(BusinessEvent<?> businessEvent) {
+        if (businessEvent instanceof BulkBusinessEvent) {
+            throw new IllegalArgumentException("BulkBusinessEvent cannot be 
raised directly");
+        }
+    }
+
+    @Override
+    public void startExternalEventRecording() {
+        eventRecordingEnabled.set(true);
+    }
+
+    @Override
+    public void stopExternalEventRecording() {
+        eventRecordingEnabled.set(false);
+        try {
+            List<BusinessEvent<?>> recordedBusinessEvents = 
recordedEvents.get();
+            if (isExternalEventPostingEnabled()) {
+                log.debug("Posting the BulkBusinessEvent for the recorded {} 
events", recordedBusinessEvents.size());
+                externalEventService.postEvent(new 
BulkBusinessEvent(recordedBusinessEvents));
+            }
+        } finally {
+            recordedEvents.remove();
+        }
+    }
 }
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
deleted file mode 100644
index fd82f36ab..000000000
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventService.java
+++ /dev/null
@@ -1,64 +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;
-
-import java.util.ArrayList;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import 
org.apache.fineract.infrastructure.event.business.domain.BulkBusinessEvent;
-import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
-import org.springframework.stereotype.Service;
-
-@Service
-@RequiredArgsConstructor
-public class DelayedExternalEventService {
-
-    private final ThreadLocal<List<BusinessEvent<?>>> localEventStorage = 
ThreadLocal.withInitial(ArrayList::new);
-
-    private final ExternalEventService delegate;
-
-    public <T> void enqueueEvent(BusinessEvent<T> event) {
-        if (event == null) {
-            throw new IllegalArgumentException("event cannot be null");
-        }
-
-        localEventStorage.get().add(event);
-    }
-
-    public boolean hasEnqueuedEvents() {
-        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()) {
-            throw new IllegalStateException("No events have been enqueued");
-        }
-
-        delegate.postEvent(new BulkBusinessEvent(enqueuedEvents));
-    }
-}
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImplTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImplTest.java
new file mode 100644
index 000000000..e1a0ad814
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImplTest.java
@@ -0,0 +1,161 @@
+/**
+ * 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.business.service;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
+import 
org.apache.fineract.infrastructure.event.business.domain.BulkBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
+import 
org.apache.fineract.infrastructure.event.external.service.ExternalEventService;
+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;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+@ExtendWith(MockitoExtension.class)
+@SuppressWarnings({ "rawtypes", "unchecked" })
+@MockitoSettings(strictness = Strictness.LENIENT)
+class BusinessEventNotifierServiceImplTest {
+
+    @Mock
+    private ExternalEventService externalEventService;
+    @Mock
+    private FineractProperties fineractProperties;
+
+    @InjectMocks
+    private BusinessEventNotifierServiceImpl underTest;
+
+    @Test
+    public void testNotifyPostBusinessEventShouldNotifyPostListeners() {
+        // given
+        configureExternalEventsProperties(false);
+
+        MockBusinessEvent event = new MockBusinessEvent();
+        BusinessEventListener<MockBusinessEvent> postListener = mockListener();
+        underTest.addPostBusinessEventListener(MockBusinessEvent.class, 
postListener);
+        // when
+        underTest.notifyPostBusinessEvent(event);
+        // then
+        verify(postListener).onBusinessEvent(event);
+        verifyNoInteractions(externalEventService);
+    }
+
+    @Test
+    public void 
testNotifyPostBusinessEventShouldNotifyPostListenersAndPostAnExternalEvent() {
+        // given
+        configureExternalEventsProperties(true);
+
+        MockBusinessEvent event = new MockBusinessEvent();
+        BusinessEventListener<MockBusinessEvent> postListener = mockListener();
+        underTest.addPostBusinessEventListener(MockBusinessEvent.class, 
postListener);
+        // when
+        underTest.notifyPostBusinessEvent(event);
+        // then
+        verify(postListener).onBusinessEvent(event);
+        verify(externalEventService).postEvent(event);
+    }
+
+    @Test
+    public void 
testNotifyPostBusinessEventShouldNotifyPostListenersAndPostAnBulkExternalEventWhenRecordingEnabled()
 {
+        // given
+        configureExternalEventsProperties(true);
+
+        MockBusinessEvent event = new MockBusinessEvent();
+        BusinessEventListener<MockBusinessEvent> postListener = mockListener();
+        underTest.addPostBusinessEventListener(MockBusinessEvent.class, 
postListener);
+        underTest.startExternalEventRecording();
+        underTest.notifyPostBusinessEvent(event);
+        // when
+        underTest.stopExternalEventRecording();
+        // then
+        verify(postListener).onBusinessEvent(event);
+
+        ArgumentCaptor<BulkBusinessEvent> argumentCaptor = 
ArgumentCaptor.forClass(BulkBusinessEvent.class);
+        verify(externalEventService).postEvent(argumentCaptor.capture());
+        BulkBusinessEvent capturedEvent = argumentCaptor.getValue();
+        assertThat(capturedEvent.get()).hasSize(1);
+        assertThat(capturedEvent.get().get(0)).isEqualTo(event);
+    }
+
+    @Test
+    public void testNotifyPreBusinessEventShouldNotifyPreListeners() {
+        // given
+        configureExternalEventsProperties(false);
+
+        MockBusinessEvent event = new MockBusinessEvent();
+        BusinessEventListener<MockBusinessEvent> preListener = mockListener();
+        underTest.addPreBusinessEventListener(MockBusinessEvent.class, 
preListener);
+        // when
+        underTest.notifyPreBusinessEvent(event);
+        // then
+        verify(preListener).onBusinessEvent(event);
+        verifyNoInteractions(externalEventService);
+    }
+
+    @Test
+    public void 
testNotifyPreBusinessEventShouldNotifyPreListenersWithoutPostingAnExternalEvent()
 {
+        // given
+        configureExternalEventsProperties(true);
+
+        MockBusinessEvent event = new MockBusinessEvent();
+        BusinessEventListener<MockBusinessEvent> preListener = mockListener();
+        underTest.addPreBusinessEventListener(MockBusinessEvent.class, 
preListener);
+        // when
+        underTest.notifyPreBusinessEvent(event);
+        // then
+        verify(preListener).onBusinessEvent(event);
+        verifyNoInteractions(externalEventService);
+    }
+
+    private void configureExternalEventsProperties(boolean 
isExternalEventsEnabled) {
+        FineractProperties.FineractEventsProperties eventsProperties = new 
FineractProperties.FineractEventsProperties();
+        FineractProperties.FineractExternalEventsProperties externalProperties 
= new FineractProperties.FineractExternalEventsProperties();
+        eventsProperties.setExternal(externalProperties);
+        externalProperties.setEnabled(isExternalEventsEnabled);
+        given(fineractProperties.getEvents()).willReturn(eventsProperties);
+    }
+
+    private BusinessEventListener<MockBusinessEvent> mockListener() {
+        return (BusinessEventListener<MockBusinessEvent>) 
mock(BusinessEventListener.class);
+    }
+
+    private static class MockBusinessEvent implements BusinessEvent<Object> {
+
+        @Override
+        public Object get() {
+            return null;
+        }
+
+        @Override
+        public String getType() {
+            return null;
+        }
+    }
+
+}
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
deleted file mode 100644
index 8ecf62f46..000000000
--- 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/DelayedExternalEventServiceTest.java
+++ /dev/null
@@ -1,116 +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;
-
-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.infrastructure.event.business.domain.BulkBusinessEvent;
-import org.apache.fineract.infrastructure.event.business.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);
-    }
-}

Reply via email to