Merge "CPS Delta API: Update action for delta service"
[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-2023 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.spi.CpsDataPersistenceService;
43 import org.onap.cps.spi.FetchDescendantsOption;
44 import org.onap.cps.spi.exceptions.DataValidationException;
45 import org.onap.cps.spi.model.Anchor;
46 import org.onap.cps.spi.model.DataNode;
47 import org.onap.cps.spi.model.DataNodeBuilder;
48 import org.onap.cps.spi.model.DeltaReport;
49 import org.onap.cps.spi.utils.CpsValidator;
50 import org.onap.cps.utils.ContentType;
51 import org.onap.cps.utils.YangParser;
52 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
53 import org.springframework.stereotype.Service;
54
55 @Service
56 @Slf4j
57 @RequiredArgsConstructor
58 public class CpsDataServiceImpl implements CpsDataService {
59
60     private static final String ROOT_NODE_XPATH = "/";
61     private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
62
63     private final CpsDataPersistenceService cpsDataPersistenceService;
64     private final CpsAnchorService cpsAnchorService;
65     private final CpsValidator cpsValidator;
66     private final YangParser yangParser;
67     private final CpsDeltaService cpsDeltaService;
68
69     @Override
70     public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
71         final OffsetDateTime observedTimestamp) {
72         saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON);
73     }
74
75     @Override
76     @Timed(value = "cps.data.service.datanode.root.save",
77         description = "Time taken to save a root data node")
78     public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
79                          final OffsetDateTime observedTimestamp, final ContentType contentType) {
80         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
81         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
82         final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType);
83         cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
84     }
85
86     @Override
87     public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
88                          final String nodeData, final OffsetDateTime observedTimestamp) {
89         saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON);
90     }
91
92     @Override
93     @Timed(value = "cps.data.service.datanode.child.save",
94         description = "Time taken to save a child data node")
95     public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
96                          final String nodeData, final OffsetDateTime observedTimestamp,
97                          final ContentType contentType) {
98         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
99         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
100         final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType);
101         cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
102     }
103
104     @Override
105     @Timed(value = "cps.data.service.list.element.save",
106         description = "Time taken to save list elements")
107     public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
108                                  final String jsonData, final OffsetDateTime observedTimestamp) {
109         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
110         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
111         final Collection<DataNode> listElementDataNodeCollection =
112             buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
113         if (isRootNodeXpath(parentNodeXpath)) {
114             cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
115         } else {
116             cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
117                                                       listElementDataNodeCollection);
118         }
119     }
120
121     @Override
122     @Timed(value = "cps.data.service.datanode.get",
123             description = "Time taken to get data nodes for an xpath")
124     public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
125                                              final String xpath,
126                                              final FetchDescendantsOption fetchDescendantsOption) {
127         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
128         return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
129     }
130
131     @Override
132     @Timed(value = "cps.data.service.datanode.batch.get",
133         description = "Time taken to get a batch of data nodes")
134     public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
135                                                               final Collection<String> xpaths,
136                                                               final FetchDescendantsOption fetchDescendantsOption) {
137         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
138         return cpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, xpaths,
139                 fetchDescendantsOption);
140     }
141
142     @Override
143     @Timed(value = "cps.data.service.datanode.leaves.update",
144         description = "Time taken to update a batch of leaf data nodes")
145     public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
146         final String jsonData, final OffsetDateTime observedTimestamp) {
147         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
148         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
149         final Collection<DataNode> dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData,
150                 ContentType.JSON);
151         final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
152                 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
153         cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
154     }
155
156     @Override
157     @Timed(value = "cps.data.service.datanode.leaves.descendants.leaves.update",
158         description = "Time taken to update data node leaves and existing descendants leaves")
159     public void updateNodeLeavesAndExistingDescendantLeaves(final String dataspaceName, final String anchorName,
160         final String parentNodeXpath,
161         final String dataNodeUpdatesAsJson,
162         final OffsetDateTime observedTimestamp) {
163         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
164         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
165         final Collection<DataNode> dataNodeUpdates =
166             buildDataNodes(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
167         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
168             processDataNodeUpdate(anchor, dataNodeUpdate);
169         }
170     }
171
172     @Override
173     public String startSession() {
174         return cpsDataPersistenceService.startSession();
175     }
176
177     @Override
178     public void closeSession(final String sessionId) {
179         cpsDataPersistenceService.closeSession(sessionId);
180     }
181
182     @Override
183     public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
184         lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
185     }
186
187     @Override
188     public void lockAnchor(final String sessionID, final String dataspaceName,
189                            final String anchorName, final Long timeoutInMilliseconds) {
190         cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
191     }
192
193     @Override
194     @Timed(value = "cps.data.service.get.delta",
195             description = "Time taken to get delta between anchors")
196     public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
197                                                            final String sourceAnchorName,
198                                                            final String targetAnchorName, final String xpath,
199                                                            final FetchDescendantsOption fetchDescendantsOption) {
200
201         final Collection<DataNode> sourceDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
202                 sourceAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
203         final Collection<DataNode> targetDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
204                 targetAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
205
206         return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
207     }
208
209     @Override
210     @Timed(value = "cps.data.service.datanode.descendants.update",
211         description = "Time taken to update a data node and descendants")
212     public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
213                                              final String parentNodeXpath, final String jsonData,
214                                              final OffsetDateTime observedTimestamp) {
215         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
216         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
217         final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
218         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
219     }
220
221     @Override
222     @Timed(value = "cps.data.service.datanode.descendants.batch.update",
223         description = "Time taken to update a batch of data nodes and descendants")
224     public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
225                                               final Map<String, String> nodesJsonData,
226                                               final OffsetDateTime observedTimestamp) {
227         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
228         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
229         final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData);
230         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
231     }
232
233     @Override
234     @Timed(value = "cps.data.service.list.update",
235         description = "Time taken to update a list")
236     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
237             final String jsonData, final OffsetDateTime observedTimestamp) {
238         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
239         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
240         final Collection<DataNode> newListElements =
241             buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
242         replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
243     }
244
245     @Override
246     @Timed(value = "cps.data.service.list.batch.update",
247         description = "Time taken to update a batch of lists")
248     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
249             final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
250         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
251         cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
252     }
253
254     @Override
255     @Timed(value = "cps.data.service.datanode.delete",
256         description = "Time taken to delete a datanode")
257     public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
258                                final OffsetDateTime observedTimestamp) {
259         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
260         cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
261     }
262
263     @Override
264     @Timed(value = "cps.data.service.datanode.batch.delete",
265         description = "Time taken to delete a batch of datanodes")
266     public void deleteDataNodes(final String dataspaceName, final String anchorName,
267                                 final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
268         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
269         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths);
270     }
271
272     @Override
273     @Timed(value = "cps.data.service.datanode.delete.anchor",
274         description = "Time taken to delete all datanodes for an anchor")
275     public void deleteDataNodes(final String dataspaceName, final String anchorName,
276                                 final OffsetDateTime observedTimestamp) {
277         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
278         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
279     }
280
281     @Override
282     @Timed(value = "cps.data.service.datanode.delete.anchor.batch",
283         description = "Time taken to delete all datanodes for multiple anchors")
284     public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames,
285                                 final OffsetDateTime observedTimestamp) {
286         cpsValidator.validateNameCharacters(dataspaceName);
287         cpsValidator.validateNameCharacters(anchorNames);
288         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames);
289     }
290
291     @Override
292     @Timed(value = "cps.data.service.list.delete",
293         description = "Time taken to delete a list or list element")
294     public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
295         final OffsetDateTime observedTimestamp) {
296         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
297         cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
298     }
299
300     private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) {
301         final Collection<DataNode> dataNodes = new ArrayList<>();
302         for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
303             dataNodes.addAll(buildDataNodes(anchor, nodeJsonData.getKey(), nodeJsonData.getValue(), ContentType.JSON));
304         }
305         return dataNodes;
306     }
307
308     private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
309                                                 final String nodeData, final ContentType contentType) {
310
311         if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
312             final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, "");
313             final Collection<DataNode> dataNodes = new DataNodeBuilder()
314                     .withContainerNode(containerNode)
315                     .buildCollection();
316             if (dataNodes.isEmpty()) {
317                 throw new DataValidationException("No data nodes.", "No data nodes provided");
318             }
319             return dataNodes;
320         }
321         final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
322         final ContainerNode containerNode =
323             yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
324         final Collection<DataNode> dataNodes = new DataNodeBuilder()
325             .withParentNodeXpath(normalizedParentNodeXpath)
326             .withContainerNode(containerNode)
327             .buildCollection();
328         if (dataNodes.isEmpty()) {
329             throw new DataValidationException("No data nodes.", "No data nodes provided");
330         }
331         return dataNodes;
332     }
333
334
335     private static boolean isRootNodeXpath(final String xpath) {
336         return ROOT_NODE_XPATH.equals(xpath);
337     }
338
339     private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
340         cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
341                 Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
342         final Collection<DataNode> childDataNodeUpdates = dataNodeUpdate.getChildDataNodes();
343         for (final DataNode childDataNodeUpdate : childDataNodeUpdates) {
344             processDataNodeUpdate(anchor, childDataNodeUpdate);
345         }
346     }
347
348 }