e2480c5e562b4a4142034eceb512ae83f773083e
[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.api.impl.events.cmsubscription.service;
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 CmNotificationSubscriptionPersistenceServiceImpl implements CmNotificationSubscriptionPersistenceService {
47
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']
51             """.trim();
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";
54
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']";
57
58
59     private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID = """
60             //filter/subscriptionIds[text()='%s']
61             """.trim();
62
63     private final JsonObjectMapper jsonObjectMapper;
64     private final CpsQueryService cpsQueryService;
65     private final CpsDataService cpsDataService;
66
67     @Override
68     public boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
69                                                        final String xpath) {
70         return !getOngoingCmNotificationSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
71     }
72
73     @Override
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();
78     }
79
80     @Override
81     public Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType,
82                                                                       final String cmHandleId, final String xpath) {
83
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();
92         }
93         return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
94     }
95
96     @Override
97     public void addCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
98                                               final String xpath, final String subscriptionId) {
99         final Collection<String> subscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
100                 cmHandleId, xpath);
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);
106         }
107     }
108
109     @Override
110     public void removeCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
111                                                  final String xpath, final String subscriptionId) {
112         final Collection<String> subscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
113                 cmHandleId, xpath);
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);
124             }
125         }
126     }
127
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);
141         }
142     }
143
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());
148     }
149
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();
155     }
156
157     private void addFirstSubscriptionForDatastoreCmHandleAndXpath(final DatastoreType datastoreType,
158                                                                   final String cmHandleId,
159                                                                   final String xpath,
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);
170         } else {
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());
175         }
176     }
177
178     private void saveSubscriptionDetails(final DatastoreType datastoreType, final String cmHandleId,
179                                          final String xpath,
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);
186     }
187
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) + "]}";
192     }
193
194     private static String escapeQuotesByDoublingThem(final String inputXpath) {
195         return inputXpath.replace("'", "''");
196     }
197 }