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);
162 private void deleteListOfSubscriptionsFor(final DatastoreType datastoreType, final String cmHandleId,
163 final String xpath) {
164 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
165 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
166 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)),
167 OffsetDateTime.now());
168 final Collection<DataNode> existingFiltersForCmHandle =
169 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
170 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
171 datastoreType.getDatastoreName(), cmHandleId),
172 DIRECT_CHILDREN_ONLY).iterator().next()
173 .getChildDataNodes();
174 if (existingFiltersForCmHandle.isEmpty()) {
175 removeCmHandleFromDatastore(datastoreType.getDatastoreName(), cmHandleId);
179 private void removeCmHandleFromDatastore(final String datastoreName, final String cmHandleId) {
180 cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
181 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, cmHandleId),
182 OffsetDateTime.now());
185 private boolean isFirstSubscriptionForCmHandle(final DatastoreType datastoreType, final String cmHandleId) {
186 return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
187 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
188 datastoreType.getDatastoreName(), cmHandleId), OMIT_DESCENDANTS).isEmpty();
191 private void addFirstSubscriptionForDatastoreCmHandleAndXpath(final DatastoreType datastoreType,
192 final String cmHandleId, final String xpath, final String subscriptionId) {
193 final Collection<String> newSubscriptionList = Collections.singletonList(subscriptionId);
194 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, newSubscriptionList);
195 if (isFirstSubscriptionForCmHandle(datastoreType, cmHandleId)) {
196 final String parentXpath =
197 "/datastores/datastore[@name='%s']/cm-handles".formatted(datastoreType.getDatastoreName());
198 final String subscriptionAsJson =
199 String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":%s}]}", cmHandleId,
200 subscriptionDetailsAsJson);
201 cpsDataService.saveData(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, subscriptionAsJson,
202 OffsetDateTime.now(), ContentType.JSON);
204 cpsDataService.saveListElements(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
205 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
206 datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson,
207 OffsetDateTime.now());
211 private void saveSubscriptionDetails(final DatastoreType datastoreType, final String cmHandleId, final String xpath,
212 final Collection<String> subscriptionIds) {
213 final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, subscriptionIds);
214 cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
215 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
216 datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, OffsetDateTime.now(),
220 private String getSubscriptionDetailsAsJson(final String xpath, final Collection<String> subscriptionIds) {
221 final Map<String, Serializable> subscriptionDetailsAsMap =
222 Map.of("xpath", xpath, "subscriptionIds", (Serializable) subscriptionIds);
223 return "{\"filter\":[" + jsonObjectMapper.asJsonString(subscriptionDetailsAsMap) + "]}";
226 private static String escapeQuotesByDoublingThem(final String inputXpath) {
227 return inputXpath.replace("'", "''");