diff options
author | Renu Kumari <renu.kumari@bell.ca> | 2021-05-27 23:16:32 -0400 |
---|---|---|
committer | Renu Kumari <renu.kumari@bell.ca> | 2021-07-07 06:54:13 -0400 |
commit | ff52b94907002d2d2910567e1ad5f55e66008eb8 (patch) | |
tree | b53ea7cf4587ec84db6db77b0d23f23327e538ad /cps-service/src/main/java/org/onap | |
parent | 6658f50ddbfbd13469f24aefc8a6002d8807a7eb (diff) |
Sending Data Updated Event to kafka
Issue-ID: CPS-374
Signed-off-by: Renu Kumari <renu.kumari@bell.ca>
Change-Id: I05fedcace42b84575411df26c586788bffe6b846
Diffstat (limited to 'cps-service/src/main/java/org/onap')
6 files changed, 353 insertions, 0 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index 23bf4f2ee9..c822c68ae9 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -3,6 +3,7 @@ * Copyright (C) 2021 Nordix Foundation * Modifications Copyright (C) 2020 Bell Canada. All rights reserved. * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021 Bell Canada. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +26,7 @@ import java.util.Collection; import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; +import org.onap.cps.notification.NotificationService; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataValidationException; @@ -53,10 +55,14 @@ public class CpsDataServiceImpl implements CpsDataService { @Autowired private YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; + @Autowired + private NotificationService notificationService; + @Override public void saveData(final String dataspaceName, final String anchorName, final String jsonData) { final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData); cpsDataPersistenceService.storeDataNode(dataspaceName, anchorName, dataNode); + notificationService.processDataUpdatedEvent(dataspaceName, anchorName); } @Override @@ -64,6 +70,7 @@ public class CpsDataServiceImpl implements CpsDataService { final String jsonData) { final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData); cpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, parentNodeXpath, dataNode); + notificationService.processDataUpdatedEvent(dataspaceName, anchorName); } @Override @@ -72,6 +79,7 @@ public class CpsDataServiceImpl implements CpsDataService { final Collection<DataNode> dataNodesCollection = buildDataNodeCollectionFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData); cpsDataPersistenceService.addListDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodesCollection); + notificationService.processDataUpdatedEvent(dataspaceName, anchorName); } @Override @@ -86,6 +94,7 @@ public class CpsDataServiceImpl implements CpsDataService { final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData); cpsDataPersistenceService .updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves()); + notificationService.processDataUpdatedEvent(dataspaceName, anchorName); } @Override @@ -93,6 +102,7 @@ public class CpsDataServiceImpl implements CpsDataService { final String jsonData) { final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData); cpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName, dataNode); + notificationService.processDataUpdatedEvent(dataspaceName, anchorName); } @Override @@ -101,6 +111,7 @@ public class CpsDataServiceImpl implements CpsDataService { final Collection<DataNode> dataNodes = buildDataNodeCollectionFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData); cpsDataPersistenceService.replaceListDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes); + notificationService.processDataUpdatedEvent(dataspaceName, anchorName); } private DataNode buildDataNodeFromJson(final String dataspaceName, final String anchorName, diff --git a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java new file mode 100644 index 0000000000..ae5e9ba290 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java @@ -0,0 +1,96 @@ +/* + * ============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.net.URI; +import java.net.URISyntaxException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; +import org.onap.cps.api.CpsAdminService; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.event.model.Content; +import org.onap.cps.event.model.CpsDataUpdatedEvent; +import org.onap.cps.event.model.CpsDataUpdatedEvent.Schema; +import org.onap.cps.event.model.Data; +import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.model.Anchor; +import org.onap.cps.spi.model.DataNode; +import org.onap.cps.utils.DataMapUtils; +import org.springframework.stereotype.Component; + +@Component +class CpsDataUpdatedEventFactory { + + private static final URI EVENT_SOURCE; + private static final String EVENT_TYPE = "org.onap.cps.data-updated-event"; + private static final DateTimeFormatter dateTimeFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + static { + try { + EVENT_SOURCE = new URI("urn:cps:org.onap.cps"); + } catch (final URISyntaxException e) { + // As it is fixed string, I don't expect to see this error + throw new IllegalArgumentException(e); + } + } + + private CpsDataService cpsDataService; + private CpsAdminService cpsAdminService; + + public CpsDataUpdatedEventFactory(final CpsDataService cpsDataService, final CpsAdminService cpsAdminService) { + this.cpsDataService = cpsDataService; + this.cpsAdminService = cpsAdminService; + } + + CpsDataUpdatedEvent createCpsDataUpdatedEvent(final String dataspaceName, final String anchorName) { + final var dataNode = cpsDataService + .getDataNode(dataspaceName, anchorName, "/", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); + final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + return toCpsDataUpdatedEvent(anchor, dataNode); + } + + private CpsDataUpdatedEvent toCpsDataUpdatedEvent(final Anchor anchor, final DataNode dataNode) { + final var cpsDataUpdatedEvent = new CpsDataUpdatedEvent(); + cpsDataUpdatedEvent.withContent(createContent(anchor, dataNode)); + cpsDataUpdatedEvent.withId(UUID.randomUUID().toString()); + cpsDataUpdatedEvent.withSchema(Schema.URN_CPS_ORG_ONAP_CPS_DATA_UPDATED_EVENT_SCHEMA_1_1_0_SNAPSHOT); + cpsDataUpdatedEvent.withSource(EVENT_SOURCE); + cpsDataUpdatedEvent.withType(EVENT_TYPE); + return cpsDataUpdatedEvent; + } + + private Data createData(final DataNode dataNode) { + final var data = new Data(); + DataMapUtils.toDataMap(dataNode).forEach((key, value) -> data.setAdditionalProperty(key, value)); + return data; + } + + private Content createContent(final Anchor anchor, final DataNode dataNode) { + final var content = new Content(); + content.withAnchorName(anchor.getName()); + content.withDataspaceName(anchor.getDataspaceName()); + content.withSchemaSetName(anchor.getSchemaSetName()); + content.withData(createData(dataNode)); + content.withObservedTimestamp(dateTimeFormatter.format(OffsetDateTime.now())); + return content; + } +} diff --git a/cps-service/src/main/java/org/onap/cps/notification/KafkaProducerListener.java b/cps-service/src/main/java/org/onap/cps/notification/KafkaProducerListener.java new file mode 100644 index 0000000000..f4b68c0699 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/notification/KafkaProducerListener.java @@ -0,0 +1,52 @@ +/* + * ============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 lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.springframework.kafka.support.ProducerListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class KafkaProducerListener<K, V> implements ProducerListener<K, V> { + + private NotificationErrorHandler notificationErrorHandler; + + public KafkaProducerListener(final NotificationErrorHandler notificationErrorHandler) { + this.notificationErrorHandler = notificationErrorHandler; + } + + @Override + public void onSuccess(final ProducerRecord<K, V> producerRecord, final RecordMetadata recordMetadata) { + log.debug("Message sent to event-bus topic :'{}' with body : {} ", producerRecord.topic(), + producerRecord.value()); + } + + @Override + public void onError(final ProducerRecord<K, V> producerRecord, + final RecordMetadata recordMetadata, + final Exception exception) { + notificationErrorHandler.onException("Failed to send message to message bus", + exception, producerRecord, recordMetadata); + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationErrorHandler.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationErrorHandler.java new file mode 100644 index 0000000000..eef028d5f3 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/notification/NotificationErrorHandler.java @@ -0,0 +1,40 @@ +/* + * ============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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class NotificationErrorHandler { + + void onException(final Exception exception, final Object... context) { + onException("Failed to process", exception, context); + } + + void onException(final String message, final Exception exception, final Object... context) { + log.error("{} \n Error cause: {} \n Error context: {}", + message, + exception.getCause() != null ? exception.getCause().toString() : exception.getMessage(), + context, + exception); + } +} diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationPublisher.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationPublisher.java new file mode 100644 index 0000000000..1ab032b57c --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/notification/NotificationPublisher.java @@ -0,0 +1,65 @@ +/* + * ============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 lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.onap.cps.event.model.CpsDataUpdatedEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class NotificationPublisher { + + private KafkaTemplate<String, CpsDataUpdatedEvent> kafkaTemplate; + private String topicName; + + /** + * Create an instance of Notification Publisher. + * + * @param kafkaTemplate kafkaTemplate is send event using kafka + * @param topicName topic, to which cpsDataUpdatedEvent is sent, is provided by setting + * 'notification.data-updated.topic' in the application properties + */ + @Autowired + public NotificationPublisher( + final KafkaTemplate<String, CpsDataUpdatedEvent> kafkaTemplate, + final @Value("${notification.data-updated.topic}") String topicName) { + this.kafkaTemplate = kafkaTemplate; + this.topicName = topicName; + } + + /** + * Send event to Kafka with correct message key. + * + * @param cpsDataUpdatedEvent event to be sent to kafka + */ + void sendNotification(@NonNull final CpsDataUpdatedEvent cpsDataUpdatedEvent) { + final var messageKey = cpsDataUpdatedEvent.getContent().getDataspaceName() + "," + + cpsDataUpdatedEvent.getContent().getAnchorName(); + log.debug("Data Updated event is being sent with messageKey: '{}' & body : {} ", + messageKey, cpsDataUpdatedEvent); + kafkaTemplate.send(topicName, messageKey, cpsDataUpdatedEvent); + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java new file mode 100644 index 0000000000..e97e8a3d8c --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java @@ -0,0 +1,89 @@ + +/* + * ============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 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 NotificationPublisher notificationPublisher; + private CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory; + private NotificationErrorHandler notificationErrorHandler; + + /** + * 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 + */ + @Autowired + public NotificationService( + @Value("${notification.data-updated.enabled}") final boolean dataUpdatedEventNotificationEnabled, + final NotificationPublisher notificationPublisher, + final CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory, + final NotificationErrorHandler notificationErrorHandler) { + this.dataUpdatedEventNotificationEnabled = dataUpdatedEventNotificationEnabled; + this.notificationPublisher = notificationPublisher; + this.cpsDataUpdatedEventFactory = cpsDataUpdatedEventFactory; + this.notificationErrorHandler = notificationErrorHandler; + } + + /** + * Process Data Updated Event and publishes the notification. + * + * @param dataspaceName dataspace name + * @param anchorName anchor name + */ + public void processDataUpdatedEvent(final String dataspaceName, final String anchorName) { + log.debug("process data updated event for dataspace '{}' & anchor '{}'", dataspaceName, anchorName); + try { + if (shouldSendNotification()) { + final var cpsDataUpdatedEvent = + cpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, anchorName); + log.debug("data updated event to be published {}", cpsDataUpdatedEvent); + notificationPublisher.sendNotification(cpsDataUpdatedEvent); + } + } catch (final Exception exception) { + /* All the exceptions are handled to not to propagate it to caller. + CPS operation should not fail if sending event fails for any reason. + */ + notificationErrorHandler.onException("Failed to process cps-data-updated-event.", + exception, dataspaceName, anchorName); + } + } + + /* + Add more complex rules based on dataspace and anchor later + */ + private boolean shouldSendNotification() { + return dataUpdatedEventNotificationEnabled; + } + +} |