Merge "Condense Liquibase steps"
[cps.git] / cps-ncmp-service / src / main / java / org / onap / cps / ncmp / api / impl / events / cmsubscription / service / CmNotificationSubscriptionPersistenceServiceImpl.java
1 /*
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
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.ncmp.api.impl.events.cmsubscription.service;
22
23 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
24 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
25
26 import java.io.Serializable;
27 import java.time.OffsetDateTime;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import lombok.RequiredArgsConstructor;
35 import lombok.extern.slf4j.Slf4j;
36 import org.onap.cps.api.CpsDataService;
37 import org.onap.cps.api.CpsQueryService;
38 import org.onap.cps.cpspath.parser.CpsPathUtil;
39 import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
40 import org.onap.cps.spi.model.DataNode;
41 import org.onap.cps.utils.ContentType;
42 import org.onap.cps.utils.JsonObjectMapper;
43 import org.springframework.stereotype.Service;
44
45 @Slf4j
46 @Service
47 @RequiredArgsConstructor
48 public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotificationSubscriptionPersistenceService {
49
50     private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions";
51     private static final String CM_SUBSCRIPTION_CPS_PATH_QUERY = """
52             /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']/filters/filter[@xpath='%s']
53             """.trim();
54     private static final String SUBSCRIPTION_IDS_CPS_PATH_QUERY = """
55             //filter/subscriptionIds[text()='%s']
56             """.trim();
57
58     private final JsonObjectMapper jsonObjectMapper;
59     private final CpsQueryService cpsQueryService;
60     private final CpsDataService cpsDataService;
61
62     @Override
63     public boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
64             final String xpath) {
65         return !getOngoingCmNotificationSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
66     }
67
68     @Override
69     public boolean isUniqueSubscriptionId(final String subscriptionId) {
70         return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
71                 SUBSCRIPTION_IDS_CPS_PATH_QUERY.formatted(subscriptionId),
72                 OMIT_DESCENDANTS).isEmpty();
73     }
74
75     @Override
76     public Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType,
77             final String cmHandleId, final String xpath) {
78
79         final String isOngoingCmSubscriptionCpsPathQuery =
80                 CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreType.getDatastoreName(), cmHandleId,
81                         escapeQuotesByDoublingThem(xpath));
82         final Collection<DataNode> existingNodes =
83                 cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
84                         isOngoingCmSubscriptionCpsPathQuery, OMIT_DESCENDANTS);
85         if (existingNodes.isEmpty()) {
86             return Collections.emptyList();
87         }
88         return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
89     }
90
91     @Override
92     public void addOrUpdateCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
93                                                       final String xpath, final String newSubscriptionId) {
94         if (isOngoingCmNotificationSubscription(datastoreType, cmHandleId, xpath)) {
95             final DataNode existingFilterNode =
96                     cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
97                             CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreType.getDatastoreName(), cmHandleId,
98                                     escapeQuotesByDoublingThem(xpath)),
99                             OMIT_DESCENDANTS).iterator().next();
100             final Collection<String> existingSubscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
101                     cmHandleId, xpath);
102             if (!existingSubscriptionIds.contains(newSubscriptionId)) {
103                 updateListOfSubscribers(existingSubscriptionIds, newSubscriptionId, existingFilterNode);
104             }
105         } else {
106             addNewSubscriptionViaDatastore(datastoreType, cmHandleId, xpath, newSubscriptionId);
107         }
108     }
109
110     private void addNewSubscriptionViaDatastore(final DatastoreType datastoreType, final String cmHandleId,
111                                                 final String xpath, final String newSubscriptionId) {
112         final String parentXpathFormat = "/datastores/datastore[@name='%s']/cm-handles";
113         String parentXpath = "";
114         if (datastoreType == PASSTHROUGH_RUNNING) {
115             parentXpath = parentXpathFormat.formatted("ncmp-datastore:passthrough-running");
116         } else {
117             parentXpath = parentXpathFormat.formatted("ncmp-datastore:passthrough-operational");
118         }
119
120         final String updatedJson = String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":{\"filter\":"
121                 + "[{\"xpath\":\"%s\",\"subscriptionIds\":[\"%s\"]}]}}]}", cmHandleId, xpath, newSubscriptionId);
122         cpsDataService.saveData(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, updatedJson,
123                 OffsetDateTime.now(), ContentType.JSON);
124     }
125
126     private void updateListOfSubscribers(final Collection<String> existingSubscriptionIds,
127                                          final String newSubscriptionId, final DataNode existingFilterNode) {
128         final String parentXpath = CpsPathUtil.getNormalizedParentXpath(existingFilterNode.getXpath());
129         final List<String> updatedSubscribers = new ArrayList<>(existingSubscriptionIds);
130         updatedSubscribers.add(newSubscriptionId);
131         final Map<String, Serializable> updatedLeaves = new HashMap<>();
132         updatedLeaves.put("xpath", existingFilterNode.getLeaves().get("xpath"));
133         updatedLeaves.put("subscriptionIds", (Serializable) updatedSubscribers);
134         final String updatedJson = "{\"filter\":[" + jsonObjectMapper.asJsonString(updatedLeaves) + "]}";
135         cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, updatedJson,
136                 OffsetDateTime.now());
137     }
138
139     private static String escapeQuotesByDoublingThem(final String inputXpath) {
140         return inputXpath.replace("'", "''");
141     }
142 }