8dcf5a5e858f49e1c0534e6315454d881123b0c7
[cps.git] /
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.impl.cmnotificationsubscription.ncmp;
22
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import java.util.stream.Collectors;
33 import lombok.RequiredArgsConstructor;
34 import org.onap.cps.api.model.DataNode;
35 import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler;
36 import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiCmSubscriptionDetailsPerDmiMapper;
37 import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiInEventMapper;
38 import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiInEventProducer;
39 import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus;
40 import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
41 import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionKey;
42 import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate;
43 import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionTuple;
44 import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService;
45 import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.Predicate;
46 import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent;
47 import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.DmiInEvent;
48 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
49 import org.springframework.stereotype.Service;
50
51 @Service
52 @RequiredArgsConstructor
53 public class CmSubscriptionHandlerImpl implements CmSubscriptionHandler {
54
55     private static final Pattern SUBSCRIPTION_KEY_FROM_XPATH_PATTERN = Pattern.compile(
56             "^/datastores/datastore\\[@name='([^']*)']/cm-handles/cm-handle\\[@id='([^']*)']/"
57                     + "filters/filter\\[@xpath='(.*)']$");
58
59     private final CmSubscriptionPersistenceService cmSubscriptionPersistenceService;
60     private final CmSubscriptionComparator cmSubscriptionComparator;
61     private final NcmpOutEventMapper ncmpOutEventMapper;
62     private final DmiInEventMapper dmiInEventMapper;
63     private final DmiCmSubscriptionDetailsPerDmiMapper dmiCmSubscriptionDetailsPerDmiMapper;
64     private final NcmpOutEventProducer ncmpOutEventProducer;
65     private final DmiInEventProducer dmiInEventProducer;
66     private final DmiCacheHandler dmiCacheHandler;
67     private final InventoryPersistence inventoryPersistence;
68
69     @Override
70     public void processSubscriptionCreateRequest(final String subscriptionId, final List<Predicate> predicates) {
71         if (cmSubscriptionPersistenceService.isUniqueSubscriptionId(subscriptionId)) {
72             dmiCacheHandler.add(subscriptionId, predicates);
73             handleNewCmSubscription(subscriptionId);
74             scheduleNcmpOutEventResponse(subscriptionId, "subscriptionCreateResponse");
75         } else {
76             rejectAndPublishCreateRequest(subscriptionId, predicates);
77         }
78     }
79
80     @Override
81     public void processSubscriptionDeleteRequest(final String subscriptionId) {
82         final Collection<DataNode> subscriptionDataNodes =
83                 cmSubscriptionPersistenceService.getAllNodesForSubscriptionId(subscriptionId);
84         final DmiCmSubscriptionTuple dmiCmSubscriptionTuple =
85                 getLastRemainingAndOverlappingSubscriptionsPerDmi(subscriptionDataNodes);
86         dmiCacheHandler.add(subscriptionId, mergeDmiCmSubscriptionDetailsPerDmiMaps(dmiCmSubscriptionTuple));
87         if (dmiCmSubscriptionTuple.lastRemainingSubscriptionsPerDmi().isEmpty()) {
88             acceptAndPublishDeleteRequest(subscriptionId);
89         } else {
90             sendSubscriptionDeleteRequestToDmi(subscriptionId,
91                     dmiCmSubscriptionDetailsPerDmiMapper.toDmiCmSubscriptionsPerDmi(
92                             dmiCmSubscriptionTuple.lastRemainingSubscriptionsPerDmi()));
93             scheduleNcmpOutEventResponse(subscriptionId, "subscriptionDeleteResponse");
94         }
95     }
96
97     private Map<String, DmiCmSubscriptionDetails> mergeDmiCmSubscriptionDetailsPerDmiMaps(
98             final DmiCmSubscriptionTuple dmiCmSubscriptionTuple) {
99         final Map<String, DmiCmSubscriptionDetails> lastRemainingDmiSubscriptionsPerDmi =
100                 dmiCmSubscriptionDetailsPerDmiMapper.toDmiCmSubscriptionsPerDmi(
101                         dmiCmSubscriptionTuple.lastRemainingSubscriptionsPerDmi());
102         final Map<String, DmiCmSubscriptionDetails> overlappingDmiSubscriptionsPerDmi =
103                 dmiCmSubscriptionDetailsPerDmiMapper.toDmiCmSubscriptionsPerDmi(
104                         dmiCmSubscriptionTuple.overlappingSubscriptionsPerDmi());
105         final Map<String, DmiCmSubscriptionDetails> mergedDmiSubscriptionsPerDmi =
106                 new HashMap<>(lastRemainingDmiSubscriptionsPerDmi);
107         overlappingDmiSubscriptionsPerDmi.forEach((dmiServiceName, dmiCmSubscriptionDetails) ->
108                 mergedDmiSubscriptionsPerDmi.merge(dmiServiceName, dmiCmSubscriptionDetails,
109                         this::mergeDmiCmSubscriptionDetails));
110         return mergedDmiSubscriptionsPerDmi;
111     }
112
113     private DmiCmSubscriptionDetails mergeDmiCmSubscriptionDetails(
114             final DmiCmSubscriptionDetails dmiCmSubscriptionDetails,
115             final DmiCmSubscriptionDetails otherDmiCmSubscriptionDetails) {
116         final List<DmiCmSubscriptionPredicate> mergedDmiCmSubscriptionPredicates =
117                 new ArrayList<>(dmiCmSubscriptionDetails.getDmiCmSubscriptionPredicates());
118         mergedDmiCmSubscriptionPredicates.addAll(otherDmiCmSubscriptionDetails.getDmiCmSubscriptionPredicates());
119         return new DmiCmSubscriptionDetails(mergedDmiCmSubscriptionPredicates, CmSubscriptionStatus.PENDING);
120     }
121
122     private void scheduleNcmpOutEventResponse(final String subscriptionId, final String eventType) {
123         ncmpOutEventProducer.publishNcmpOutEvent(subscriptionId, eventType, null, true);
124     }
125
126     private void rejectAndPublishCreateRequest(final String subscriptionId, final List<Predicate> predicates) {
127         final Set<String> subscriptionTargetFilters =
128                 predicates.stream().flatMap(predicate -> predicate.getTargetFilter().stream())
129                         .collect(Collectors.toSet());
130         final NcmpOutEvent ncmpOutEvent = ncmpOutEventMapper.toNcmpOutEventForRejectedRequest(subscriptionId,
131                 new ArrayList<>(subscriptionTargetFilters));
132         ncmpOutEventProducer.publishNcmpOutEvent(subscriptionId, "subscriptionCreateResponse", ncmpOutEvent, false);
133     }
134
135     private void acceptAndPublishDeleteRequest(final String subscriptionId) {
136         final Set<String> dmiServiceNames = dmiCacheHandler.get(subscriptionId).keySet();
137         for (final String dmiServiceName : dmiServiceNames) {
138             dmiCacheHandler.updateDmiSubscriptionStatus(subscriptionId, dmiServiceName,
139                     CmSubscriptionStatus.ACCEPTED);
140             dmiCacheHandler.removeFromDatabase(subscriptionId, dmiServiceName);
141         }
142         final NcmpOutEvent ncmpOutEvent = ncmpOutEventMapper.toNcmpOutEvent(subscriptionId,
143                 dmiCacheHandler.get(subscriptionId));
144         ncmpOutEventProducer.publishNcmpOutEvent(subscriptionId, "subscriptionDeleteResponse", ncmpOutEvent,
145                 false);
146     }
147
148     private void handleNewCmSubscription(final String subscriptionId) {
149         final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi =
150                 dmiCacheHandler.get(subscriptionId);
151         dmiSubscriptionsPerDmi.forEach((dmiPluginName, dmiSubscriptionDetails) -> {
152             final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates =
153                     cmSubscriptionComparator.getNewDmiSubscriptionPredicates(
154                             dmiSubscriptionDetails.getDmiCmSubscriptionPredicates());
155
156             if (dmiCmSubscriptionPredicates.isEmpty()) {
157                 acceptAndPersistCmSubscriptionPerDmi(subscriptionId, dmiPluginName);
158             } else {
159                 publishDmiInEventPerDmi(subscriptionId, dmiPluginName, dmiCmSubscriptionPredicates);
160             }
161         });
162     }
163
164     private void publishDmiInEventPerDmi(final String subscriptionId, final String dmiPluginName,
165                                          final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) {
166         final DmiInEvent dmiInEvent = dmiInEventMapper.toDmiInEvent(dmiCmSubscriptionPredicates);
167         dmiInEventProducer.publishDmiInEvent(subscriptionId, dmiPluginName,
168                 "subscriptionCreateRequest", dmiInEvent);
169     }
170
171     private void acceptAndPersistCmSubscriptionPerDmi(final String subscriptionId, final String dmiPluginName) {
172         dmiCacheHandler.updateDmiSubscriptionStatus(subscriptionId, dmiPluginName,
173                 CmSubscriptionStatus.ACCEPTED);
174         dmiCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName);
175     }
176
177     private void sendSubscriptionDeleteRequestToDmi(final String subscriptionId,
178                                                     final Map<String, DmiCmSubscriptionDetails>
179                                                             dmiCmSubscriptionsPerDmi) {
180         dmiCmSubscriptionsPerDmi.forEach((dmiPluginName, dmiCmSubscriptionDetails) -> {
181             final DmiInEvent dmiInEvent =
182                     dmiInEventMapper.toDmiInEvent(
183                             dmiCmSubscriptionDetails.getDmiCmSubscriptionPredicates());
184             dmiInEventProducer.publishDmiInEvent(subscriptionId,
185                     dmiPluginName, "subscriptionDeleteRequest", dmiInEvent);
186         });
187     }
188
189
190     private DmiCmSubscriptionTuple getLastRemainingAndOverlappingSubscriptionsPerDmi(
191             final Collection<DataNode> subscriptionNodes) {
192         final Map<String, Collection<DmiCmSubscriptionKey>> lastRemainingSubscriptionsPerDmi = new HashMap<>();
193         final Map<String, Collection<DmiCmSubscriptionKey>> overlappingSubscriptionsPerDmi = new HashMap<>();
194
195         for (final DataNode subscriptionNode : subscriptionNodes) {
196             final DmiCmSubscriptionKey dmiCmSubscriptionKey = extractCmSubscriptionKey(subscriptionNode.getXpath());
197             final String dmiServiceName = inventoryPersistence.getYangModelCmHandle(
198                     dmiCmSubscriptionKey.cmHandleId()).getDmiServiceName();
199             final List<String> subscribers = (List<String>) subscriptionNode.getLeaves().get("subscriptionIds");
200             populateDmiCmSubscriptionTuple(subscribers, overlappingSubscriptionsPerDmi,
201                     lastRemainingSubscriptionsPerDmi, dmiServiceName, dmiCmSubscriptionKey);
202         }
203         return new DmiCmSubscriptionTuple(lastRemainingSubscriptionsPerDmi, overlappingSubscriptionsPerDmi);
204     }
205
206     private static void populateDmiCmSubscriptionTuple(final List<String> subscribers,
207                                                        final Map<String, Collection<DmiCmSubscriptionKey>>
208                                                                overlappingSubscriptionsPerDmi,
209                                                        final Map<String, Collection<DmiCmSubscriptionKey>>
210                                                                lastRemainingSubscriptionsPerDmi,
211                                                        final String dmiServiceName,
212                                                        final DmiCmSubscriptionKey dmiCmSubscriptionKey) {
213         final Map<String, Collection<DmiCmSubscriptionKey>> targetMap =
214                 subscribers.size() > 1 ? overlappingSubscriptionsPerDmi : lastRemainingSubscriptionsPerDmi;
215         targetMap.computeIfAbsent(dmiServiceName, dmiName -> new HashSet<>()).add(dmiCmSubscriptionKey);
216     }
217
218     private DmiCmSubscriptionKey extractCmSubscriptionKey(final String xpath) {
219         final Matcher matcher = SUBSCRIPTION_KEY_FROM_XPATH_PATTERN.matcher(xpath);
220         if (matcher.find()) {
221             final String datastoreName = matcher.group(1);
222             final String cmHandleId = matcher.group(2);
223             final String filterXpath = matcher.group(3);
224             return new DmiCmSubscriptionKey(datastoreName, cmHandleId, filterXpath);
225         }
226         throw new IllegalArgumentException("DataNode xpath does not represent a subscription key");
227     }
228
229 }