2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Nordix Foundation
4 * Modifications Copyright (C) 2024 TechMahindra Ltd.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.cps.ncmp.api.impl.events.cmsubscription.service;
24 import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
25 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
27 import java.io.Serializable;
28 import java.time.OffsetDateTime;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.List;
33 import lombok.RequiredArgsConstructor;
34 import lombok.extern.slf4j.Slf4j;
35 import org.onap.cps.api.CpsDataService;
36 import org.onap.cps.api.CpsQueryService;
37 import org.onap.cps.ncmp.api.data.models.DatastoreType;
38 import org.onap.cps.spi.model.DataNode;
39 import org.onap.cps.utils.ContentType;
40 import org.onap.cps.utils.JsonObjectMapper;
41 import org.springframework.stereotype.Service;
45 @RequiredArgsConstructor
46 public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotificationSubscriptionPersistenceService {
48 private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions";
49 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE = """
50 /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']
52 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE =
53 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE + "/filters";
55 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH =
56 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE + "/filter[@xpath='%s']";
59 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID = """
60 //filter/subscriptionIds[text()='%s']
63 private final JsonObjectMapper jsonObjectMapper;
64 private final CpsQueryService cpsQueryService;
65 private final CpsDataService cpsDataService;
68 public boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
70 return !getOngoingCmNotificationSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
74 public boolean isUniqueSubscriptionId(final String subscriptionId) {
75 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
76 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId),
77 OMIT_DESCENDANTS).isEmpty();
81 public Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType,
82 final String cmHandleId, final String xpath) {
84 final String isOngoingCmSubscriptionCpsPathQuery =
85 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
86 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath));
87 final Collection<DataNode> existingNodes =
88 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
89 isOngoingCmSubscriptionCpsPathQuery, OMIT_DESCENDANTS);
90 if (existingNodes.isEmpty()) {
91 return Collections.emptyList();
93 return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
97 public void addCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
98 final String xpath, final String subscriptionId) {
99 final Collection<String> subscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
101 if (subscriptionIds.isEmpty()) {
102 addFirstSubscriptionForDatastoreCmHandleAndXpath(datastoreType, cmHandleId, xpath, subscriptionId);
103 } else if (!subscriptionIds.contains(subscriptionId)) {
104 subscriptionIds.add(subscriptionId);
105 saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
110 public void removeCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
111 final String xpath, final String subscriptionId) {
112 final Collection<String> subscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
114 if (subscriptionIds.remove(subscriptionId)) {
115 saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
116 log.info("There are subscribers left for the following cps path {} :",
117 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
118 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
119 if (subscriptionIds.isEmpty()) {
120 log.info("No subscribers left for the following cps path {} :",
121 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
122 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
123 deleteListOfSubscriptionsFor(datastoreType, cmHandleId, xpath);
128 private void deleteListOfSubscriptionsFor(final DatastoreType datastoreType, final String cmHandleId,
129 final String xpath) {
130 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
131 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
132 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)),
133 OffsetDateTime.now());
134 final Collection<DataNode> existingFiltersForCmHandle =
135 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
136 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
137 datastoreType.getDatastoreName(), cmHandleId),
138 DIRECT_CHILDREN_ONLY).iterator().next().getChildDataNodes();
139 if (existingFiltersForCmHandle.isEmpty()) {
140 removeCmHandleFromDatastore(datastoreType.getDatastoreName(), cmHandleId);
144 private void removeCmHandleFromDatastore(final String datastoreName, final String cmHandleId) {
145 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
146 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(
147 datastoreName, cmHandleId), OffsetDateTime.now());
150 private boolean isFirstSubscriptionForCmHandle(final DatastoreType datastoreType, final String cmHandleId) {
151 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
152 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
153 datastoreType.getDatastoreName(), cmHandleId),
154 OMIT_DESCENDANTS).isEmpty();
157 private void addFirstSubscriptionForDatastoreCmHandleAndXpath(final DatastoreType datastoreType,
158 final String cmHandleId,
160 final String subscriptionId) {
161 final Collection<String> newSubscriptionList = Collections.singletonList(subscriptionId);
162 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, newSubscriptionList);
163 if (isFirstSubscriptionForCmHandle(datastoreType, cmHandleId)) {
164 final String parentXpath = "/datastores/datastore[@name='%s']/cm-handles"
165 .formatted(datastoreType.getDatastoreName());
166 final String subscriptionAsJson = String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":%s}]}",
167 cmHandleId, subscriptionDetailsAsJson);
168 cpsDataService.saveData(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, subscriptionAsJson,
169 OffsetDateTime.now(), ContentType.JSON);
171 cpsDataService.saveListElements(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
172 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
173 datastoreType.getDatastoreName(), cmHandleId),
174 subscriptionDetailsAsJson, OffsetDateTime.now());
178 private void saveSubscriptionDetails(final DatastoreType datastoreType, final String cmHandleId,
180 final Collection<String> subscriptionIds) {
181 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, subscriptionIds);
182 cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
183 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
184 datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson,
185 OffsetDateTime.now(), ContentType.JSON);
188 private String getSubscriptionDetailsAsJson(final String xpath, final Collection<String> subscriptionIds) {
189 final Map<String, Serializable> subscriptionDetailsAsMap =
190 Map.of("xpath", xpath, "subscriptionIds", (Serializable) subscriptionIds);
191 return "{\"filter\":[" + jsonObjectMapper.asJsonString(subscriptionDetailsAsMap) + "]}";
194 private static String escapeQuotesByDoublingThem(final String inputXpath) {
195 return inputXpath.replace("'", "''");