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.impl.cmnotificationsubscription.utils;
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 CmSubscriptionPersistenceService {
48 private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
49 private static final String CM_SUBSCRIPTIONS_ANCHOR_NAME = "cm-data-subscriptions";
51 private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions";
52 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE = """
53 /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']
55 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE =
56 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE + "/filters";
58 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH =
59 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE + "/filter[@xpath='%s']";
62 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID = """
63 //filter/subscriptionIds[text()='%s']
66 private final JsonObjectMapper jsonObjectMapper;
67 private final CpsQueryService cpsQueryService;
68 private final CpsDataService cpsDataService;
71 * Check if we have an ongoing cm subscription based on the parameters.
73 * @param datastoreType the susbcription target datastore type
74 * @param cmHandleId the id of the cm handle for the susbcription
75 * @param xpath the target xpath
76 * @return true for ongoing cmsubscription , otherwise false
78 public boolean isOngoingCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
80 return !getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
84 * Check if the subscription ID is unique against ongoing subscriptions.
86 * @param subscriptionId subscription ID
87 * @return true if subscriptionId is not used in active subscriptions, otherwise false
89 public boolean isUniqueSubscriptionId(final String subscriptionId) {
90 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
91 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId), OMIT_DESCENDANTS).isEmpty();
95 * Get all ongoing cm notification subscription based on the parameters.
97 * @param datastoreType the susbcription target datastore type
98 * @param cmHandleId the id of the cm handle for the susbcription
99 * @param xpath the target xpath
100 * @return collection of subscription ids of ongoing cm notification subscription
102 public Collection<String> getOngoingCmSubscriptionIds(final DatastoreType datastoreType,
103 final String cmHandleId, final String xpath) {
105 final String isOngoingCmSubscriptionCpsPathQuery =
106 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
107 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath));
108 final Collection<DataNode> existingNodes =
109 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
110 isOngoingCmSubscriptionCpsPathQuery, OMIT_DESCENDANTS);
111 if (existingNodes.isEmpty()) {
112 return Collections.emptyList();
114 return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
118 * Add cm notification subscription.
120 * @param datastoreType the susbcription target datastore type
121 * @param cmHandleId the id of the cm handle for the susbcription
122 * @param xpath the target xpath
123 * @param newSubscriptionId subscription id to be added
125 public void addCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
126 final String xpath, final String newSubscriptionId) {
127 final Collection<String> subscriptionIds =
128 getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath);
129 if (subscriptionIds.isEmpty()) {
130 addFirstSubscriptionForDatastoreCmHandleAndXpath(datastoreType, cmHandleId, xpath, newSubscriptionId);
131 } else if (!subscriptionIds.contains(newSubscriptionId)) {
132 subscriptionIds.add(newSubscriptionId);
133 saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
138 * Remove cm notification Subscription.
140 * @param datastoreType the susbcription target datastore type
141 * @param cmHandleId the id of the cm handle for the susbcription
142 * @param xpath the target xpath
143 * @param subscriptionId subscription id to remove
145 public void removeCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
146 final String xpath, final String subscriptionId) {
147 final Collection<String> subscriptionIds =
148 getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath);
149 if (subscriptionIds.remove(subscriptionId)) {
150 saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
151 log.info("There are subscribers left for the following cps path {} :",
152 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
153 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
154 if (subscriptionIds.isEmpty()) {
155 log.info("No subscribers left for the following cps path {} :",
156 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
157 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
158 deleteListOfSubscriptionsFor(datastoreType, cmHandleId, xpath);
164 * Retrieve all existing dataNodes for given subscription id.
166 * @param subscriptionId subscription id
167 * @return collection of DataNodes
169 public Collection<DataNode> getAllNodesForSubscriptionId(final String subscriptionId) {
170 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
171 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId),
175 private void deleteListOfSubscriptionsFor(final DatastoreType datastoreType, final String cmHandleId,
176 final String xpath) {
177 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
178 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
179 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)),
180 OffsetDateTime.now());
181 final Collection<DataNode> existingFiltersForCmHandle =
182 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
183 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
184 datastoreType.getDatastoreName(), cmHandleId),
185 DIRECT_CHILDREN_ONLY).iterator().next()
186 .getChildDataNodes();
187 if (existingFiltersForCmHandle.isEmpty()) {
188 removeCmHandleFromDatastore(datastoreType.getDatastoreName(), cmHandleId);
192 private void removeCmHandleFromDatastore(final String datastoreName, final String cmHandleId) {
193 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
194 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, cmHandleId),
195 OffsetDateTime.now());
198 private boolean isFirstSubscriptionForCmHandle(final DatastoreType datastoreType, final String cmHandleId) {
199 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
200 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
201 datastoreType.getDatastoreName(), cmHandleId), OMIT_DESCENDANTS).isEmpty();
204 private void addFirstSubscriptionForDatastoreCmHandleAndXpath(final DatastoreType datastoreType,
205 final String cmHandleId, final String xpath, final String subscriptionId) {
206 final Collection<String> newSubscriptionList = Collections.singletonList(subscriptionId);
207 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, newSubscriptionList);
208 if (isFirstSubscriptionForCmHandle(datastoreType, cmHandleId)) {
209 final String parentXpath =
210 "/datastores/datastore[@name='%s']/cm-handles".formatted(datastoreType.getDatastoreName());
211 final String subscriptionAsJson =
212 String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":%s}]}", cmHandleId,
213 subscriptionDetailsAsJson);
214 cpsDataService.saveData(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, subscriptionAsJson,
215 OffsetDateTime.now(), ContentType.JSON);
217 cpsDataService.saveListElements(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
218 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
219 datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson,
220 OffsetDateTime.now(), ContentType.JSON);
224 private void saveSubscriptionDetails(final DatastoreType datastoreType, final String cmHandleId, final String xpath,
225 final Collection<String> subscriptionIds) {
226 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, subscriptionIds);
227 cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
228 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
229 datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, OffsetDateTime.now(),
233 private String getSubscriptionDetailsAsJson(final String xpath, final Collection<String> subscriptionIds) {
234 final Map<String, Serializable> subscriptionDetailsAsMap =
235 Map.of("xpath", xpath, "subscriptionIds", (Serializable) subscriptionIds);
236 return "{\"filter\":[" + jsonObjectMapper.asJsonString(subscriptionDetailsAsMap) + "]}";
239 private static String escapeQuotesByDoublingThem(final String inputXpath) {
240 return inputXpath.replace("'", "''");