Filter data updated events based on configured pattern 37/123337/4
authorRenu Kumari <renu.kumari@bell.ca>
Tue, 17 Aug 2021 18:06:53 +0000 (14:06 -0400)
committerRenu Kumari <renu.kumari@bell.ca>
Tue, 17 Aug 2021 19:16:10 +0000 (15:16 -0400)
Issue-ID: CPS-469
Signed-off-by: Renu Kumari <renu.kumari@bell.ca>
Change-Id: I7810990b54c3140677184ea671164b8835a6afbb

cps-application/src/main/resources/application.yml
cps-service/pom.xml
cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/notification/NotificationService.java
cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
cps-service/src/test/resources/application.yml

index ac620f6..91c421b 100644 (file)
@@ -72,12 +72,14 @@ notification:
     data-updated:\r
         enabled: false\r
         topic: ${CPS_CHANGE_EVENT_TOPIC:cps.cfg-state-events}\r
+        filters:\r
+            enabled-dataspaces: ${DATASPACE_FILTER_PATTERNS:""}\r
 \r
 springdoc:\r
     swagger-ui:\r
         url: /openapi.yml\r
         path: /swagger-ui/index.html\r
-        \r
+\r
 security:\r
     # comma-separated uri patterns which do not require authorization\r
     permit-uri: /manage/**,/swagger-ui/**,/swagger-resources/**,/v3/api-docs\r
index 6856f7c..c69ead0 100644 (file)
       <groupId>org.springframework</groupId>\r
       <artifactId>spring-context</artifactId>\r
     </dependency>\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-starter-validation</artifactId>\r
+    </dependency>\r
     <dependency>\r
       <!-- For parsing JSON object -->\r
       <groupId>com.google.code.gson</groupId>\r
diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java
new file mode 100644 (file)
index 0000000..eb75e3f
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Bell Canada. All rights reserved.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.notification;
+
+import java.util.Collections;
+import java.util.Map;
+import javax.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+@ConfigurationProperties(prefix = "notification.data-updated")
+@Component
+@Data
+@Validated
+public class NotificationProperties {
+
+    @NotNull
+    private String topic;
+    private Map<String, String> filters = Collections.emptyMap();
+    @NotNull
+    private boolean enabled = false;
+}
index 67fc54b..9cb2c52 100644 (file)
 
 package org.onap.cps.notification;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 @Service
 @Slf4j
 public class NotificationService {
 
-    private boolean dataUpdatedEventNotificationEnabled;
+    private NotificationProperties notificationProperties;
     private NotificationPublisher notificationPublisher;
     private CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory;
     private NotificationErrorHandler notificationErrorHandler;
+    private List<Pattern> dataspacePatterns;
 
     /**
      * Create an instance of Notification Subscriber.
      *
-     * @param dataUpdatedEventNotificationEnabled   notification can be enabled by setting
-     *                                              'notification.data-updated.enabled=true' in application properties
-     * @param notificationPublisher                 notification Publisher
-     * @param cpsDataUpdatedEventFactory            to create CPSDataUpdatedEvent
-     * @param notificationErrorHandler              error handler
+     * @param notificationProperties     properties for notification
+     * @param notificationPublisher      notification Publisher
+     * @param cpsDataUpdatedEventFactory to create CPSDataUpdatedEvent
+     * @param notificationErrorHandler   error handler
      */
     @Autowired
     public NotificationService(
-        @Value("${notification.data-updated.enabled}") final boolean dataUpdatedEventNotificationEnabled,
+        final NotificationProperties notificationProperties,
         final NotificationPublisher notificationPublisher,
         final CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory,
         final NotificationErrorHandler notificationErrorHandler) {
-        this.dataUpdatedEventNotificationEnabled = dataUpdatedEventNotificationEnabled;
+        this.notificationProperties = notificationProperties;
         this.notificationPublisher = notificationPublisher;
         this.cpsDataUpdatedEventFactory = cpsDataUpdatedEventFactory;
         this.notificationErrorHandler = notificationErrorHandler;
+        this.dataspacePatterns = getDataspaceFilterPatterns(notificationProperties);
+    }
+
+    private List<Pattern> getDataspaceFilterPatterns(final NotificationProperties notificationProperties) {
+        if (notificationProperties.isEnabled()) {
+            return Arrays.stream(notificationProperties.getFilters()
+                .getOrDefault("enabled-dataspaces", "")
+                .split(","))
+                .map(filterPattern -> Pattern.compile(filterPattern, Pattern.CASE_INSENSITIVE))
+                .collect(Collectors.toList());
+        } else {
+            return Collections.emptyList();
+        }
     }
 
     /**
@@ -62,7 +79,7 @@ public class NotificationService {
     public void processDataUpdatedEvent(final String dataspaceName, final String anchorName) {
         log.debug("process data updated event for dataspace '{}' & anchor '{}'", dataspaceName, anchorName);
         try {
-            if (shouldSendNotification()) {
+            if (shouldSendNotification(dataspaceName)) {
                 final var cpsDataUpdatedEvent =
                     cpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, anchorName);
                 log.debug("data updated event to be published {}", cpsDataUpdatedEvent);
@@ -80,8 +97,11 @@ public class NotificationService {
     /*
         Add more complex rules based on dataspace and anchor later
      */
-    private boolean shouldSendNotification() {
-        return dataUpdatedEventNotificationEnabled;
+    private boolean shouldSendNotification(final String dataspaceName) {
+
+        return notificationProperties.isEnabled()
+            && dataspacePatterns.stream()
+            .anyMatch(pattern -> pattern.matcher(dataspaceName).find());
     }
 
 }
index a742795..b60d093 100644 (file)
 package org.onap.cps.notification
 
 import org.onap.cps.event.model.CpsDataUpdatedEvent
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Shared
 import spock.lang.Specification
 
+@SpringBootTest
+@EnableConfigurationProperties
+@ContextConfiguration(classes = [NotificationProperties])
 class NotificationServiceSpec extends Specification {
 
-    def mockNotificationPublisher = Mock(NotificationPublisher)
-    def spyNotificationErrorHandler = Spy(new NotificationErrorHandler())
-    def mockCpsDataUpdatedEventFactory = Mock(CpsDataUpdatedEventFactory)
+    @SpringBean
+    NotificationPublisher mockNotificationPublisher = Mock()
+    @SpringBean
+    NotificationErrorHandler spyNotificationErrorHandler = Spy(new NotificationErrorHandler())
+    @SpringBean
+    CpsDataUpdatedEventFactory mockCpsDataUpdatedEventFactory = Mock()
 
-    def objectUnderTest = new NotificationService(true, mockNotificationPublisher,
-            mockCpsDataUpdatedEventFactory, spyNotificationErrorHandler)
+    @Autowired
+    NotificationProperties notificationProperties
+    NotificationProperties spyNotificationProperties
 
-    def myDataspaceName = 'my-dataspace'
+    @Shared
+    def myDataspacePublishedName = 'my-dataspace-published'
     def myAnchorName = 'my-anchorname'
 
     def 'Skip sending notification when disabled.'() {
-
         given: 'notification is disabled'
-            objectUnderTest.dataUpdatedEventNotificationEnabled = false
-
+            def objectUnderTest = createNotificationService(false)
         when: 'dataUpdatedEvent is received'
-            objectUnderTest.processDataUpdatedEvent(myDataspaceName, myAnchorName)
-
+            objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName)
         then: 'the notification is not sent'
             0 * mockNotificationPublisher.sendNotification(_)
     }
 
-    def 'Send notification when enabled.'() {
-
+    def 'Send notification when enabled: #scenario.'() {
         given: 'notification is enabled'
-            objectUnderTest.dataUpdatedEventNotificationEnabled = true
+            def objectUnderTest = createNotificationService(true)
         and: 'event factory can create event successfully'
             def cpsDataUpdatedEvent = new CpsDataUpdatedEvent()
-            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspaceName, myAnchorName) >> cpsDataUpdatedEvent
-
+            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, myAnchorName) >> cpsDataUpdatedEvent
         when: 'dataUpdatedEvent is received'
-            objectUnderTest.processDataUpdatedEvent(myDataspaceName, myAnchorName)
-
-        then: 'notification is sent with correct event'
-            1 * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent)
+            objectUnderTest.processDataUpdatedEvent(dataspaceName, myAnchorName)
+        then: 'notification is sent'
+            expectedSendNotificationCount * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent)
+        where:
+            scenario                               | dataspaceName            || expectedSendNotificationCount
+            'dataspace name does not match filter' | 'does-not-match-pattern' || 0
+            'dataspace name matches filter'        | myDataspacePublishedName || 1
     }
 
-    def 'Error handling in notification service.'(){
-        given: 'event factory can not create event successfully'
-            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspaceName, myAnchorName) >>
-                    { throw new Exception("Could not create event") }
-
+    def 'Error handling in notification service.'() {
+        given: 'notification is enabled'
+            def objectUnderTest = createNotificationService(true)
+        and: 'event factory can not create event successfully'
+            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspacePublishedName, myAnchorName) >>
+                { throw new Exception("Could not create event") }
         when: 'event is sent for processing'
-            objectUnderTest.processDataUpdatedEvent(myDataspaceName, myAnchorName)
-
+            objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName)
         then: 'error is handled and not thrown to caller'
             notThrown Exception
-            1 * spyNotificationErrorHandler.onException(_,_,_,_)
-
+            1 * spyNotificationErrorHandler.onException(_, _, _, _)
     }
 
+    NotificationService createNotificationService(boolean notificationEnabled) {
+        spyNotificationProperties = Spy(notificationProperties)
+        spyNotificationProperties.isEnabled() >> notificationEnabled
+        return new NotificationService(spyNotificationProperties, mockNotificationPublisher,
+            mockCpsDataUpdatedEventFactory, spyNotificationErrorHandler)
+    }
 }
index c934486..94f7e81 100644 (file)
 
 notification:
   data-updated:
-    topic: cps-event
+    filters:
+      enabled-dataspaces: ".*-published,.*-important"
     enabled: true
+    topic: cps-event
 
 spring:
   kafka: