2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.ncmp.impl.cmnotificationsubscription.utils;
23 import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
24 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
26 import java.io.Serializable;
27 import java.time.OffsetDateTime;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.List;
32 import lombok.RequiredArgsConstructor;
33 import lombok.extern.slf4j.Slf4j;
34 import org.onap.cps.api.CpsDataService;
35 import org.onap.cps.api.CpsQueryService;
36 import org.onap.cps.ncmp.api.data.models.DatastoreType;
37 import org.onap.cps.spi.model.DataNode;
38 import org.onap.cps.utils.ContentType;
39 import org.onap.cps.utils.JsonObjectMapper;
40 import org.springframework.stereotype.Service;
44 @RequiredArgsConstructor
45 public class CmSubscriptionPersistenceService {
47 private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
48 private static final String CM_SUBSCRIPTIONS_ANCHOR_NAME = "cm-data-subscriptions";
50 private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions";
51 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE = """
52 /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']
54 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE =
55 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE + "/filters";
57 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH =
58 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE + "/filter[@xpath='%s']";
61 private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID = """
62 //filter/subscriptionIds[text()='%s']
65 private final JsonObjectMapper jsonObjectMapper;
66 private final CpsQueryService cpsQueryService;
67 private final CpsDataService cpsDataService;
70 * Check if we have an ongoing cm subscription based on the parameters.
72 * @param datastoreType the susbcription target datastore type
73 * @param cmHandleId the id of the cm handle for the susbcription
74 * @param xpath the target xpath
75 * @return true for ongoing cmsubscription , otherwise false
77 public boolean isOngoingCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
79 return !getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
83 * Check if the subscription ID is unique against ongoing subscriptions.
85 * @param subscriptionId subscription ID
86 * @return true if subscriptionId is not used in active subscriptions, otherwise false
88 public boolean isUniqueSubscriptionId(final String subscriptionId) {
89 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
90 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId), OMIT_DESCENDANTS).isEmpty();
94 * Get all ongoing cm notification subscription based on the parameters.
96 * @param datastoreType the susbcription target datastore type
97 * @param cmHandleId the id of the cm handle for the susbcription
98 * @param xpath the target xpath
99 * @return collection of subscription ids of ongoing cm notification subscription
101 public Collection<String> getOngoingCmSubscriptionIds(final DatastoreType datastoreType,
102 final String cmHandleId, final String xpath) {
104 final String isOngoingCmSubscriptionCpsPathQuery =
105 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
106 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath));
107 final Collection<DataNode> existingNodes =
108 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
109 isOngoingCmSubscriptionCpsPathQuery, OMIT_DESCENDANTS);
110 if (existingNodes.isEmpty()) {
111 return Collections.emptyList();
113 return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
117 * Add cm notification subscription.
119 * @param datastoreType the susbcription target datastore type
120 * @param cmHandleId the id of the cm handle for the susbcription
121 * @param xpath the target xpath
122 * @param newSubscriptionId subscription id to be added
124 public void addCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
125 final String xpath, final String newSubscriptionId) {
126 final Collection<String> subscriptionIds =
127 getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath);
128 if (subscriptionIds.isEmpty()) {
129 addFirstSubscriptionForDatastoreCmHandleAndXpath(datastoreType, cmHandleId, xpath, newSubscriptionId);
130 } else if (!subscriptionIds.contains(newSubscriptionId)) {
131 subscriptionIds.add(newSubscriptionId);
132 saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
137 * Remove cm notification Subscription.
139 * @param datastoreType the susbcription target datastore type
140 * @param cmHandleId the id of the cm handle for the susbcription
141 * @param xpath the target xpath
142 * @param subscriptionId subscription id to remove
144 public void removeCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
145 final String xpath, final String subscriptionId) {
146 final Collection<String> subscriptionIds =
147 getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath);
148 if (subscriptionIds.remove(subscriptionId)) {
149 saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
150 log.info("There are subscribers left for the following cps path {} :",
151 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
152 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
153 if (subscriptionIds.isEmpty()) {
154 log.info("No subscribers left for the following cps path {} :",
155 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
156 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
157 deleteListOfSubscriptionsFor(datastoreType, cmHandleId, xpath);
163 * Retrieve all existing dataNodes for given subscription id.
165 * @param subscriptionId subscription id
166 * @return collection of DataNodes
168 public Collection<DataNode> getAllNodesForSubscriptionId(final String subscriptionId) {
169 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
170 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId),
174 private void deleteListOfSubscriptionsFor(final DatastoreType datastoreType, final String cmHandleId,
175 final String xpath) {
176 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
177 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
178 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)),
179 OffsetDateTime.now());
180 final Collection<DataNode> existingFiltersForCmHandle =
181 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
182 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
183 datastoreType.getDatastoreName(), cmHandleId),
184 DIRECT_CHILDREN_ONLY).iterator().next()
185 .getChildDataNodes();
186 if (existingFiltersForCmHandle.isEmpty()) {
187 removeCmHandleFromDatastore(datastoreType.getDatastoreName(), cmHandleId);
191 private void removeCmHandleFromDatastore(final String datastoreName, final String cmHandleId) {
192 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
193 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, cmHandleId),
194 OffsetDateTime.now());
197 private boolean isFirstSubscriptionForCmHandle(final DatastoreType datastoreType, final String cmHandleId) {
198 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
199 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
200 datastoreType.getDatastoreName(), cmHandleId), OMIT_DESCENDANTS).isEmpty();
203 private void addFirstSubscriptionForDatastoreCmHandleAndXpath(final DatastoreType datastoreType,
204 final String cmHandleId, final String xpath, final String subscriptionId) {
205 final Collection<String> newSubscriptionList = Collections.singletonList(subscriptionId);
206 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, newSubscriptionList);
207 if (isFirstSubscriptionForCmHandle(datastoreType, cmHandleId)) {
208 final String parentXpath =
209 "/datastores/datastore[@name='%s']/cm-handles".formatted(datastoreType.getDatastoreName());
210 final String subscriptionAsJson =
211 String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":%s}]}", cmHandleId,
212 subscriptionDetailsAsJson);
213 cpsDataService.saveData(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, subscriptionAsJson,
214 OffsetDateTime.now(), ContentType.JSON);
216 cpsDataService.saveListElements(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
217 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
218 datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson,
219 OffsetDateTime.now());
223 private void saveSubscriptionDetails(final DatastoreType datastoreType, final String cmHandleId, final String xpath,
224 final Collection<String> subscriptionIds) {
225 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, subscriptionIds);
226 cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
227 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
228 datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, OffsetDateTime.now(),
232 private String getSubscriptionDetailsAsJson(final String xpath, final Collection<String> subscriptionIds) {
233 final Map<String, Serializable> subscriptionDetailsAsMap =
234 Map.of("xpath", xpath, "subscriptionIds", (Serializable) subscriptionIds);
235 return "{\"filter\":[" + jsonObjectMapper.asJsonString(subscriptionDetailsAsMap) + "]}";
238 private static String escapeQuotesByDoublingThem(final String inputXpath) {
239 return inputXpath.replace("'", "''");