6b5ed908b88f26406305d7c79e3d934c8017b277
[cps.git] /
1 /*
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
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.ncmp.impl.cmnotificationsubscription.utils;
23
24 import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
25 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
26
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;
32 import java.util.Map;
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;
42
43 @Slf4j
44 @Service
45 @RequiredArgsConstructor
46 public class CmSubscriptionPersistenceService {
47
48     private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
49     private static final String CM_SUBSCRIPTIONS_ANCHOR_NAME = "cm-data-subscriptions";
50
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']
54             """.trim();
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";
57
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']";
60
61
62     private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID = """
63             //filter/subscriptionIds[text()='%s']
64             """.trim();
65
66     private final JsonObjectMapper jsonObjectMapper;
67     private final CpsQueryService cpsQueryService;
68     private final CpsDataService cpsDataService;
69
70     /**
71      * Check if we have an ongoing cm subscription based on the parameters.
72      *
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
77      */
78     public boolean isOngoingCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
79             final String xpath) {
80         return !getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
81     }
82
83     /**
84      * Check if the subscription ID is unique against ongoing subscriptions.
85      *
86      * @param subscriptionId subscription ID
87      * @return true if subscriptionId is not used in active subscriptions, otherwise false
88      */
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();
92     }
93
94     /**
95      * Get all ongoing cm notification subscription based on the parameters.
96      *
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
101      */
102     public Collection<String> getOngoingCmSubscriptionIds(final DatastoreType datastoreType,
103             final String cmHandleId, final String xpath) {
104
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();
113         }
114         return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
115     }
116
117     /**
118      * Add cm notification subscription.
119      *
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
124      */
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);
134         }
135     }
136
137     /**
138      * Remove cm notification Subscription.
139      *
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
144      */
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);
159             }
160         }
161     }
162
163     /**
164      * Retrieve all existing dataNodes for given subscription id.
165      *
166      * @param subscriptionId  subscription id
167      * @return collection of DataNodes
168      */
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),
172                 OMIT_DESCENDANTS);
173     }
174
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);
189         }
190     }
191
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());
196     }
197
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();
202     }
203
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);
216         } else {
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);
221         }
222     }
223
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(),
230                 ContentType.JSON);
231     }
232
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) + "]}";
237     }
238
239     private static String escapeQuotesByDoublingThem(final String inputXpath) {
240         return inputXpath.replace("'", "''");
241     }
242
243 }
244