Merge "re-enable Operation Too Large Exception: limit 200"
[cps.git] / cps-service / src / main / java / org / onap / cps / api / impl / CpsDataServiceImpl.java
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2024 Nordix Foundation
4  *  Modifications Copyright (C) 2020-2022 Bell Canada.
5  *  Modifications Copyright (C) 2021 Pantheon.tech
6  *  Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
7  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
8  *  ================================================================================
9  *  Licensed under the Apache License, Version 2.0 (the "License");
10  *  you may not use this file except in compliance with the License.
11  *  You may obtain a copy of the License at
12  *
13  *        http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *  Unless required by applicable law or agreed to in writing, software
16  *  distributed under the License is distributed on an "AS IS" BASIS,
17  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  *  See the License for the specific language governing permissions and
19  *  limitations under the License.
20  *
21  *  SPDX-License-Identifier: Apache-2.0
22  *  ============LICENSE_END=========================================================
23  */
24
25 package org.onap.cps.api.impl;
26
27 import io.micrometer.core.annotation.Timed;
28 import java.io.Serializable;
29 import java.time.OffsetDateTime;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.stream.Collectors;
36 import lombok.RequiredArgsConstructor;
37 import lombok.extern.slf4j.Slf4j;
38 import org.onap.cps.api.CpsAnchorService;
39 import org.onap.cps.api.CpsDataService;
40 import org.onap.cps.api.CpsDeltaService;
41 import org.onap.cps.cpspath.parser.CpsPathUtil;
42 import org.onap.cps.events.CpsDataUpdateEventsService;
43 import org.onap.cps.events.model.Data.Operation;
44 import org.onap.cps.spi.CpsDataPersistenceService;
45 import org.onap.cps.spi.FetchDescendantsOption;
46 import org.onap.cps.spi.exceptions.DataValidationException;
47 import org.onap.cps.spi.model.Anchor;
48 import org.onap.cps.spi.model.DataNode;
49 import org.onap.cps.spi.model.DataNodeBuilder;
50 import org.onap.cps.spi.model.DeltaReport;
51 import org.onap.cps.spi.utils.CpsValidator;
52 import org.onap.cps.utils.ContentType;
53 import org.onap.cps.utils.YangParser;
54 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
55 import org.springframework.stereotype.Service;
56
57 @Service
58 @Slf4j
59 @RequiredArgsConstructor
60 public class CpsDataServiceImpl implements CpsDataService {
61
62     private static final String ROOT_NODE_XPATH = "/";
63     private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
64
65     private final CpsDataPersistenceService cpsDataPersistenceService;
66     private final CpsDataUpdateEventsService cpsDataUpdateEventsService;
67     private final CpsAnchorService cpsAnchorService;
68
69     private final CpsValidator cpsValidator;
70     private final YangParser yangParser;
71     private final CpsDeltaService cpsDeltaService;
72
73     @Override
74     public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
75         final OffsetDateTime observedTimestamp) {
76         saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON);
77     }
78
79     @Override
80     @Timed(value = "cps.data.service.datanode.root.save",
81         description = "Time taken to save a root data node")
82     public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
83                          final OffsetDateTime observedTimestamp, final ContentType contentType) {
84         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
85         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
86         final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType);
87         cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
88         sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.CREATE, observedTimestamp);
89     }
90
91     @Override
92     public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
93                          final String nodeData, final OffsetDateTime observedTimestamp) {
94         saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON);
95     }
96
97     @Override
98     @Timed(value = "cps.data.service.datanode.child.save",
99         description = "Time taken to save a child data node")
100     public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
101                          final String nodeData, final OffsetDateTime observedTimestamp,
102                          final ContentType contentType) {
103         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
104         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
105         final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType);
106         cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
107         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp);
108     }
109
110     @Override
111     @Timed(value = "cps.data.service.list.element.save",
112         description = "Time taken to save list elements")
113     public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
114                                  final String jsonData, final OffsetDateTime observedTimestamp) {
115         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
116         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
117         final Collection<DataNode> listElementDataNodeCollection =
118             buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
119         if (isRootNodeXpath(parentNodeXpath)) {
120             cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
121         } else {
122             cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
123                                                       listElementDataNodeCollection);
124         }
125         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
126     }
127
128     @Override
129     @Timed(value = "cps.data.service.datanode.get",
130             description = "Time taken to get data nodes for an xpath")
131     public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
132                                              final String xpath,
133                                              final FetchDescendantsOption fetchDescendantsOption) {
134         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
135         return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
136     }
137
138     @Override
139     @Timed(value = "cps.data.service.datanode.batch.get",
140         description = "Time taken to get a batch of data nodes")
141     public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
142                                                               final Collection<String> xpaths,
143                                                               final FetchDescendantsOption fetchDescendantsOption) {
144         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
145         return cpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, xpaths,
146                 fetchDescendantsOption);
147     }
148
149     @Override
150     @Timed(value = "cps.data.service.datanode.leaves.update",
151         description = "Time taken to update a batch of leaf data nodes")
152     public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
153         final String jsonData, final OffsetDateTime observedTimestamp) {
154         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
155         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
156         final Collection<DataNode> dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData,
157                 ContentType.JSON);
158         final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
159                 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
160         cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
161         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
162     }
163
164     @Override
165     @Timed(value = "cps.data.service.datanode.leaves.descendants.leaves.update",
166         description = "Time taken to update data node leaves and existing descendants leaves")
167     public void updateNodeLeavesAndExistingDescendantLeaves(final String dataspaceName, final String anchorName,
168         final String parentNodeXpath,
169         final String dataNodeUpdatesAsJson,
170         final OffsetDateTime observedTimestamp) {
171         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
172         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
173         final Collection<DataNode> dataNodeUpdates =
174             buildDataNodes(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
175         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
176             processDataNodeUpdate(anchor, dataNodeUpdate);
177         }
178         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
179     }
180
181     @Override
182     public String startSession() {
183         return cpsDataPersistenceService.startSession();
184     }
185
186     @Override
187     public void closeSession(final String sessionId) {
188         cpsDataPersistenceService.closeSession(sessionId);
189     }
190
191     @Override
192     public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
193         lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
194     }
195
196     @Override
197     public void lockAnchor(final String sessionID, final String dataspaceName,
198                            final String anchorName, final Long timeoutInMilliseconds) {
199         cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
200     }
201
202     @Override
203     @Timed(value = "cps.data.service.get.delta",
204             description = "Time taken to get delta between anchors")
205     public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
206                                                            final String sourceAnchorName,
207                                                            final String targetAnchorName, final String xpath,
208                                                            final FetchDescendantsOption fetchDescendantsOption) {
209
210         final Collection<DataNode> sourceDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
211                 sourceAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
212         final Collection<DataNode> targetDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
213                 targetAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
214
215         return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
216     }
217
218     @Override
219     @Timed(value = "cps.data.service.datanode.descendants.update",
220         description = "Time taken to update a data node and descendants")
221     public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
222                                              final String parentNodeXpath, final String jsonData,
223                                              final OffsetDateTime observedTimestamp) {
224         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
225         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
226         final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
227         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
228         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
229     }
230
231     @Override
232     @Timed(value = "cps.data.service.datanode.descendants.batch.update",
233         description = "Time taken to update a batch of data nodes and descendants")
234     public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
235                                               final Map<String, String> nodesJsonData,
236                                               final OffsetDateTime observedTimestamp) {
237         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
238         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
239         final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData);
240         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
241         nodesJsonData.keySet().forEach(nodeXpath ->
242                 sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp));
243     }
244
245     @Override
246     @Timed(value = "cps.data.service.list.update",
247         description = "Time taken to update a list")
248     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
249             final String jsonData, final OffsetDateTime observedTimestamp) {
250         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
251         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
252         final Collection<DataNode> newListElements =
253             buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
254         replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
255     }
256
257     @Override
258     @Timed(value = "cps.data.service.list.batch.update",
259         description = "Time taken to update a batch of lists")
260     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
261             final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
262         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
263         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
264         cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
265         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
266     }
267
268     @Override
269     @Timed(value = "cps.data.service.datanode.delete",
270         description = "Time taken to delete a datanode")
271     public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
272                                final OffsetDateTime observedTimestamp) {
273         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
274         cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
275         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
276         sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp);
277     }
278
279     @Override
280     @Timed(value = "cps.data.service.datanode.batch.delete",
281         description = "Time taken to delete a batch of datanodes")
282     public void deleteDataNodes(final String dataspaceName, final String anchorName,
283                                 final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
284         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
285         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths);
286         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
287         dataNodeXpaths.forEach(dataNodeXpath ->
288                 sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp));
289     }
290
291
292     @Override
293     @Timed(value = "cps.data.service.datanode.delete.anchor",
294         description = "Time taken to delete all datanodes for an anchor")
295     public void deleteDataNodes(final String dataspaceName, final String anchorName,
296                                 final OffsetDateTime observedTimestamp) {
297         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
298         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
299         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
300         sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
301     }
302
303     @Override
304     @Timed(value = "cps.data.service.datanode.delete.anchor.batch",
305         description = "Time taken to delete all datanodes for multiple anchors")
306     public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames,
307                                 final OffsetDateTime observedTimestamp) {
308         cpsValidator.validateNameCharacters(dataspaceName);
309         cpsValidator.validateNameCharacters(anchorNames);
310         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames);
311         for (final Anchor anchor : cpsAnchorService.getAnchors(dataspaceName, anchorNames)) {
312             sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
313         }
314     }
315
316     @Override
317     @Timed(value = "cps.data.service.list.delete",
318         description = "Time taken to delete a list or list element")
319     public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
320         final OffsetDateTime observedTimestamp) {
321         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
322         cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
323         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
324         sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp);
325     }
326
327     private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) {
328         final Collection<DataNode> dataNodes = new ArrayList<>();
329         for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
330             dataNodes.addAll(buildDataNodes(anchor, nodeJsonData.getKey(), nodeJsonData.getValue(), ContentType.JSON));
331         }
332         return dataNodes;
333     }
334
335     private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
336                                                 final String nodeData, final ContentType contentType) {
337
338         if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
339             final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, "");
340             final Collection<DataNode> dataNodes = new DataNodeBuilder()
341                     .withContainerNode(containerNode)
342                     .buildCollection();
343             if (dataNodes.isEmpty()) {
344                 throw new DataValidationException("No data nodes.", "No data nodes provided");
345             }
346             return dataNodes;
347         }
348         final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
349         final ContainerNode containerNode =
350             yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
351         final Collection<DataNode> dataNodes = new DataNodeBuilder()
352             .withParentNodeXpath(normalizedParentNodeXpath)
353             .withContainerNode(containerNode)
354             .buildCollection();
355         if (dataNodes.isEmpty()) {
356             throw new DataValidationException("No data nodes.", "No data nodes provided");
357         }
358         return dataNodes;
359     }
360
361
362     private static boolean isRootNodeXpath(final String xpath) {
363         return ROOT_NODE_XPATH.equals(xpath);
364     }
365
366     private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
367         cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
368                 Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
369         final Collection<DataNode> childDataNodeUpdates = dataNodeUpdate.getChildDataNodes();
370         for (final DataNode childDataNodeUpdate : childDataNodeUpdates) {
371             processDataNodeUpdate(anchor, childDataNodeUpdate);
372         }
373     }
374
375     private void sendDataUpdatedEvent(final Anchor anchor, final String xpath,
376                                       final Operation operation, final OffsetDateTime observedTimestamp) {
377         try {
378             cpsDataUpdateEventsService.publishCpsDataUpdateEvent(anchor, xpath, operation, observedTimestamp);
379         } catch (final Exception exception) {
380             log.error("Failed to send message to notification service", exception);
381         }
382     }
383 }