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