dcba2753ffa8352b6c59ea58a672d10059a67ab3
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
4  *  Modifications Copyright (C) 2022 Bell Canada
5  *  Modifications Copyright (C) 2024 TechMahindra Ltd.
6  *  ================================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  *
19  *  SPDX-License-Identifier: Apache-2.0
20  *  ============LICENSE_END=========================================================
21  */
22
23 package org.onap.cps.ncmp.impl.inventory;
24
25 import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
26 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND;
27 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST;
28 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID;
29 import static org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.ADDITIONAL_PROPERTY;
30 import static org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.PUBLIC_PROPERTY;
31
32 import com.google.common.collect.ImmutableMap;
33 import com.hazelcast.map.IMap;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.HashSet;
37 import java.util.LinkedHashMap;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
42 import lombok.RequiredArgsConstructor;
43 import lombok.extern.slf4j.Slf4j;
44 import org.apache.commons.lang3.StringUtils;
45 import org.onap.cps.api.CpsDataService;
46 import org.onap.cps.api.exceptions.DataNodeNotFoundException;
47 import org.onap.cps.api.exceptions.DataValidationException;
48 import org.onap.cps.api.model.DataNode;
49 import org.onap.cps.impl.DataNodeBuilder;
50 import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse;
51 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
52 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
53 import org.onap.cps.ncmp.impl.inventory.sync.lcm.CmHandleTransitionPair;
54 import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsHelper;
55 import org.onap.cps.ncmp.impl.utils.YangDataConverter;
56 import org.onap.cps.utils.JsonObjectMapper;
57 import org.springframework.beans.factory.annotation.Qualifier;
58 import org.springframework.stereotype.Service;
59
60 @Slf4j
61 @Service
62 @RequiredArgsConstructor
63 //Accepting the security hotspot as the string checked is generated from inside code and not user input.
64 @SuppressWarnings("squid:S5852")
65 public class CmHandleRegistrationServicePropertyHandler {
66
67     private final InventoryPersistence inventoryPersistence;
68     private final CpsDataService cpsDataService;
69     private final JsonObjectMapper jsonObjectMapper;
70     private final AlternateIdChecker alternateIdChecker;
71     @Qualifier("cmHandleIdPerAlternateId")
72     private final IMap<String, String> cmHandleIdPerAlternateId;
73     private final LcmEventsHelper lcmEventsHelper;
74
75     /**
76      * Iterates over incoming updatedNcmpServiceCmHandles and update the dataNodes based on the updated attributes.
77      * The attributes which are not passed will remain as is.
78      *
79      * @param updatedNcmpServiceCmHandles collection of CmHandles
80      */
81     public List<CmHandleRegistrationResponse> updateCmHandleProperties(
82             final Collection<NcmpServiceCmHandle> updatedNcmpServiceCmHandles) {
83         final Collection<String> rejectedCmHandleIds = alternateIdChecker
84             .getIdsOfCmHandlesWithRejectedAlternateId(updatedNcmpServiceCmHandles, AlternateIdChecker.Operation.UPDATE);
85         final List<CmHandleRegistrationResponse> failureResponses =
86             CmHandleRegistrationResponse.createFailureResponses(rejectedCmHandleIds, CM_HANDLE_ALREADY_EXIST);
87         final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(failureResponses);
88         for (final NcmpServiceCmHandle updatedNcmpServiceCmHandle : updatedNcmpServiceCmHandles) {
89             final String cmHandleId = updatedNcmpServiceCmHandle.getCmHandleId();
90             if (!rejectedCmHandleIds.contains(cmHandleId)) {
91                 try {
92                     final DataNode existingCmHandleDataNode = inventoryPersistence
93                             .getCmHandleDataNodeByCmHandleId(cmHandleId, INCLUDE_ALL_DESCENDANTS).iterator().next();
94                     processUpdates(existingCmHandleDataNode, updatedNcmpServiceCmHandle);
95                     cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId));
96                 } catch (final DataNodeNotFoundException e) {
97                     log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}", cmHandleId,
98                             e.getMessage());
99                     cmHandleRegistrationResponses.add(
100                             CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND));
101                 } catch (final DataValidationException e) {
102                     log.error("Unable to update cm handle : {}, caused by : {}", cmHandleId, e.getMessage());
103                     cmHandleRegistrationResponses.add(
104                             CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLE_INVALID_ID));
105                 } catch (final Exception exception) {
106                     log.error("Unable to update cmHandle : {} , caused by : {}", cmHandleId, exception.getMessage());
107                     cmHandleRegistrationResponses.add(
108                             CmHandleRegistrationResponse.createFailureResponse(cmHandleId, exception));
109                 }
110             }
111         }
112         return cmHandleRegistrationResponses;
113     }
114
115     private void processUpdates(final DataNode existingCmHandleDataNode,
116                                 final NcmpServiceCmHandle updatedNcmpServiceCmHandle) {
117         updateAlternateId(updatedNcmpServiceCmHandle);
118         updateDataProducerIdentifier(existingCmHandleDataNode, updatedNcmpServiceCmHandle);
119         if (!updatedNcmpServiceCmHandle.getPublicProperties().isEmpty()) {
120             updateProperties(existingCmHandleDataNode, PUBLIC_PROPERTY,
121                 updatedNcmpServiceCmHandle.getPublicProperties());
122         }
123         if (!updatedNcmpServiceCmHandle.getAdditionalProperties().isEmpty()) {
124             updateProperties(existingCmHandleDataNode, ADDITIONAL_PROPERTY,
125                 updatedNcmpServiceCmHandle.getAdditionalProperties());
126         }
127     }
128
129     private void updateAlternateId(final NcmpServiceCmHandle ncmpServiceCmHandle) {
130         final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
131         final String newAlternateId = ncmpServiceCmHandle.getAlternateId();
132         if (StringUtils.isNotBlank(newAlternateId)) {
133             inventoryPersistence.updateCmHandleField(
134                     ncmpServiceCmHandle.getCmHandleId(),
135                     "alternate-id",
136                     newAlternateId);
137             cmHandleIdPerAlternateId.delete(cmHandleId);
138             cmHandleIdPerAlternateId.set(newAlternateId, cmHandleId);
139         }
140     }
141
142     private void updateDataProducerIdentifier(final DataNode cmHandleDataNode,
143                                               final NcmpServiceCmHandle ncmpServiceCmHandle) {
144         final String targetDataProducerIdentifier = ncmpServiceCmHandle.getDataProducerIdentifier();
145         final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
146
147         if (StringUtils.isBlank(targetDataProducerIdentifier)) {
148             log.warn("Ignoring update for cmHandle {}: target dataProducerIdentifier is null or blank.", cmHandleId);
149             return;
150         }
151
152         final YangModelCmHandle currentYangModelCmHandle = YangDataConverter.toYangModelCmHandle(cmHandleDataNode);
153         final String currentDataProducerIdentifier = currentYangModelCmHandle.getDataProducerIdentifier();
154
155         if (currentDataProducerIdentifier.equals(targetDataProducerIdentifier)) {
156             log.debug("Ignoring update as dataProducerIdentifier for cmHandle {} is already set to {}.", cmHandleId,
157                     targetDataProducerIdentifier);
158             return;
159         }
160
161         inventoryPersistence.updateCmHandleField(
162                 cmHandleId,
163                 "data-producer-identifier",
164                 targetDataProducerIdentifier);
165         log.debug("dataProducerIdentifier for cmHandle {} updated from {} to {}", cmHandleId,
166                 currentDataProducerIdentifier, targetDataProducerIdentifier);
167         sendLcmEventForDataProducerIdentifier(cmHandleId, currentYangModelCmHandle);
168     }
169
170     private void sendLcmEventForDataProducerIdentifier(final String cmHandleId,
171                                                        final YangModelCmHandle currentYangModelCmHandle) {
172         final YangModelCmHandle updatedYangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
173         final CmHandleTransitionPair cmHandleTransitionPair =
174             new CmHandleTransitionPair(currentYangModelCmHandle, updatedYangModelCmHandle);
175         lcmEventsHelper.sendLcmEventBatchAsynchronously(List.of(cmHandleTransitionPair));
176     }
177
178     private void updateProperties(final DataNode existingCmHandleDataNode, final PropertyType propertyType,
179                                   final Map<String, String> updatedProperties) {
180         final Collection<DataNode> replacementPropertyDataNodes =
181                 getReplacementDataNodes(existingCmHandleDataNode, propertyType, updatedProperties);
182         replacementPropertyDataNodes.addAll(
183                 getUnchangedPropertyDataNodes(existingCmHandleDataNode, propertyType, updatedProperties));
184         if (replacementPropertyDataNodes.isEmpty()) {
185             removeAllProperties(existingCmHandleDataNode, propertyType);
186         } else {
187             inventoryPersistence.replaceListContent(existingCmHandleDataNode.getXpath(), replacementPropertyDataNodes);
188         }
189     }
190
191     private void removeAllProperties(final DataNode existingCmHandleDataNode, final PropertyType propertyType) {
192         existingCmHandleDataNode.getChildDataNodes().forEach(dataNode -> {
193             final Matcher matcher = propertyType.propertyXpathPattern.matcher(dataNode.getXpath());
194             if (matcher.find()) {
195                 log.info("Deleting dataNode with xpath : [{}]", dataNode.getXpath());
196                 inventoryPersistence.deleteDataNode(dataNode.getXpath());
197             }
198         });
199     }
200
201     private Collection<DataNode> getUnchangedPropertyDataNodes(final DataNode existingCmHandleDataNode,
202                                                                final PropertyType propertyType,
203                                                                final Map<String, String> updatedProperties) {
204         final Collection<DataNode> unchangedPropertyDataNodes = new HashSet<>();
205         for (final DataNode existingPropertyDataNode : existingCmHandleDataNode.getChildDataNodes()) {
206             final Matcher matcher = propertyType.propertyXpathPattern.matcher(existingPropertyDataNode.getXpath());
207             if (matcher.find()) {
208                 final String keyName = matcher.group(2);
209                 if (!updatedProperties.containsKey(keyName)) {
210                     unchangedPropertyDataNodes.add(existingPropertyDataNode);
211                 }
212             }
213         }
214         return unchangedPropertyDataNodes;
215     }
216
217     private Collection<DataNode> getReplacementDataNodes(final DataNode existingCmHandleDataNode,
218                                                          final PropertyType propertyType,
219                                                          final Map<String, String> updatedProperties) {
220         final Collection<DataNode> replacementPropertyDataNodes = new HashSet<>();
221         updatedProperties.forEach((updatedAttributeKey, updatedAttributeValue) -> {
222             final String propertyXpath = getAttributeXpath(existingCmHandleDataNode, propertyType, updatedAttributeKey);
223             if (updatedAttributeValue != null) {
224                 log.info("Creating a new DataNode with xpath {} , key : {} and value : {}", propertyXpath,
225                         updatedAttributeKey, updatedAttributeValue);
226                 replacementPropertyDataNodes.add(
227                         buildDataNode(propertyXpath, updatedAttributeKey, updatedAttributeValue));
228             }
229         });
230         return replacementPropertyDataNodes;
231     }
232
233     private String getAttributeXpath(final DataNode cmHandle, final PropertyType propertyType,
234                                      final String attributeKey) {
235         return cmHandle.getXpath() + "/" + propertyType.xpathPrefix + String.format("[@name='%s']", attributeKey);
236     }
237
238     private DataNode buildDataNode(final String xpath, final String attributeKey, final String attributeValue) {
239         final Map<String, String> updatedLeaves = new LinkedHashMap<>(1);
240         updatedLeaves.put("name", attributeKey);
241         updatedLeaves.put("value", attributeValue);
242         log.debug("Building a new node with xpath {} with leaves (name : {} , value : {})", xpath, attributeKey,
243                 attributeValue);
244         return new DataNodeBuilder().withXpath(xpath).withLeaves(ImmutableMap.copyOf(updatedLeaves)).build();
245     }
246
247     enum PropertyType {
248         ADDITIONAL_PROPERTY("additional-properties"), PUBLIC_PROPERTY("public-properties");
249
250         private static final String LIST_INDEX_PATTERN = "\\[@(\\w+)[^\\/]'([^']+)']";
251
252         final String xpathPrefix;
253         final Pattern propertyXpathPattern;
254
255         PropertyType(final String xpathPrefix) {
256             this.xpathPrefix = xpathPrefix;
257             this.propertyXpathPattern = Pattern.compile(xpathPrefix + LIST_INDEX_PATTERN);
258         }
259     }
260 }