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);
- }
-}