Remove the dependency-cycle between beans
[cps.git] / cps-service / src / main / java / org / onap / cps / api / impl / CpsDataServiceImpl.java
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2023 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 a list element")
109     public void saveListElements(final String dataspaceName, final String anchorName,
110         final String parentNodeXpath, 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.list.element.batch.save",
125         description = "Time taken to save a batch of list elements")
126     public void saveListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
127             final Collection<String> jsonDataList, final OffsetDateTime observedTimestamp) {
128         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
129         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
130         final Collection<Collection<DataNode>> listElementDataNodeCollections =
131                 buildDataNodes(anchor, parentNodeXpath, jsonDataList, ContentType.JSON);
132         cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath,
133                 listElementDataNodeCollections);
134     }
135
136     @Override
137     @Timed(value = "cps.data.service.datanode.get",
138             description = "Time taken to get data nodes for an xpath")
139     public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
140                                              final String xpath,
141                                              final FetchDescendantsOption fetchDescendantsOption) {
142         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
143         return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
144     }
145
146     @Override
147     @Timed(value = "cps.data.service.datanode.batch.get",
148         description = "Time taken to get a batch of data nodes")
149     public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
150                                                               final Collection<String> xpaths,
151                                                               final FetchDescendantsOption fetchDescendantsOption) {
152         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
153         return cpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, xpaths,
154                 fetchDescendantsOption);
155     }
156
157     @Override
158     @Timed(value = "cps.data.service.datanode.leaves.update",
159         description = "Time taken to update a batch of leaf data nodes")
160     public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
161         final String jsonData, final OffsetDateTime observedTimestamp) {
162         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
163         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
164         final Collection<DataNode> dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData,
165                 ContentType.JSON);
166         final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
167                 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
168         cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
169     }
170
171     @Override
172     @Timed(value = "cps.data.service.datanode.leaves.descendants.leaves.update",
173         description = "Time taken to update data node leaves and existing descendants leaves")
174     public void updateNodeLeavesAndExistingDescendantLeaves(final String dataspaceName, final String anchorName,
175         final String parentNodeXpath,
176         final String dataNodeUpdatesAsJson,
177         final OffsetDateTime observedTimestamp) {
178         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
179         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
180         final Collection<DataNode> dataNodeUpdates =
181             buildDataNodes(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
182         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
183             processDataNodeUpdate(anchor, dataNodeUpdate);
184         }
185     }
186
187     @Override
188     public String startSession() {
189         return cpsDataPersistenceService.startSession();
190     }
191
192     @Override
193     public void closeSession(final String sessionId) {
194         cpsDataPersistenceService.closeSession(sessionId);
195     }
196
197     @Override
198     public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
199         lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
200     }
201
202     @Override
203     public void lockAnchor(final String sessionID, final String dataspaceName,
204                            final String anchorName, final Long timeoutInMilliseconds) {
205         cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
206     }
207
208     @Override
209     @Timed(value = "cps.data.service.get.delta",
210             description = "Time taken to get delta between anchors")
211     public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
212                                                            final String sourceAnchorName,
213                                                            final String targetAnchorName, final String xpath,
214                                                            final FetchDescendantsOption fetchDescendantsOption) {
215
216         final Collection<DataNode> sourceDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
217                 sourceAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
218         final Collection<DataNode> targetDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
219                 targetAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
220
221         return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
222     }
223
224     @Override
225     @Timed(value = "cps.data.service.datanode.descendants.update",
226         description = "Time taken to update a data node and descendants")
227     public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
228                                              final String parentNodeXpath, final String jsonData,
229                                              final OffsetDateTime observedTimestamp) {
230         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
231         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
232         final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
233         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
234     }
235
236     @Override
237     @Timed(value = "cps.data.service.datanode.descendants.batch.update",
238         description = "Time taken to update a batch of data nodes and descendants")
239     public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
240                                               final Map<String, String> nodesJsonData,
241                                               final OffsetDateTime observedTimestamp) {
242         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
243         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
244         final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData);
245         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
246     }
247
248     @Override
249     @Timed(value = "cps.data.service.list.update",
250         description = "Time taken to update a list")
251     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
252             final String jsonData, final OffsetDateTime observedTimestamp) {
253         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
254         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
255         final Collection<DataNode> newListElements =
256             buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
257         replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
258     }
259
260     @Override
261     @Timed(value = "cps.data.service.list.batch.update",
262         description = "Time taken to update a batch of lists")
263     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
264             final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
265         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
266         cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
267     }
268
269     @Override
270     @Timed(value = "cps.data.service.datanode.delete",
271         description = "Time taken to delete a datanode")
272     public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
273                                final OffsetDateTime observedTimestamp) {
274         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
275         cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
276     }
277
278     @Override
279     @Timed(value = "cps.data.service.datanode.batch.delete",
280         description = "Time taken to delete a batch of datanodes")
281     public void deleteDataNodes(final String dataspaceName, final String anchorName,
282                                 final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
283         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
284         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths);
285     }
286
287     @Override
288     @Timed(value = "cps.data.service.datanode.delete.anchor",
289         description = "Time taken to delete all datanodes for an anchor")
290     public void deleteDataNodes(final String dataspaceName, final String anchorName,
291                                 final OffsetDateTime observedTimestamp) {
292         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
293         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
294     }
295
296     @Override
297     @Timed(value = "cps.data.service.datanode.delete.anchor.batch",
298         description = "Time taken to delete all datanodes for multiple anchors")
299     public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames,
300                                 final OffsetDateTime observedTimestamp) {
301         cpsValidator.validateNameCharacters(dataspaceName);
302         cpsValidator.validateNameCharacters(anchorNames);
303         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames);
304     }
305
306     @Override
307     @Timed(value = "cps.data.service.list.delete",
308         description = "Time taken to delete a list or list element")
309     public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
310         final OffsetDateTime observedTimestamp) {
311         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
312         cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
313     }
314
315     private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) {
316         final Collection<DataNode> dataNodes = new ArrayList<>();
317         for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
318             dataNodes.addAll(buildDataNodes(anchor, nodeJsonData.getKey(), nodeJsonData.getValue(), ContentType.JSON));
319         }
320         return dataNodes;
321     }
322
323     private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
324                                                 final String nodeData, final ContentType contentType) {
325         final SchemaContext schemaContext = getSchemaContext(anchor);
326
327         if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
328             final ContainerNode containerNode = timedYangParser.parseData(contentType, nodeData, schemaContext);
329             final Collection<DataNode> dataNodes = new DataNodeBuilder()
330                     .withContainerNode(containerNode)
331                     .buildCollection();
332             if (dataNodes.isEmpty()) {
333                 throw new DataValidationException("No data nodes.", "No data nodes provided");
334             }
335             return dataNodes;
336         }
337         final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
338         final ContainerNode containerNode =
339             timedYangParser.parseData(contentType, nodeData, schemaContext, normalizedParentNodeXpath);
340         final Collection<DataNode> dataNodes = new DataNodeBuilder()
341             .withParentNodeXpath(normalizedParentNodeXpath)
342             .withContainerNode(containerNode)
343             .buildCollection();
344         if (dataNodes.isEmpty()) {
345             throw new DataValidationException("No data nodes.", "No data nodes provided");
346         }
347         return dataNodes;
348     }
349
350     private Collection<Collection<DataNode>> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
351                                                             final Collection<String> nodeDataList,
352                                                             final ContentType contentType) {
353         return nodeDataList.stream()
354             .map(nodeData -> buildDataNodes(anchor, parentNodeXpath, nodeData, contentType))
355             .collect(Collectors.toList());
356     }
357
358     private SchemaContext getSchemaContext(final Anchor anchor) {
359         return yangTextSchemaSourceSetCache
360             .get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext();
361     }
362
363     private static boolean isRootNodeXpath(final String xpath) {
364         return ROOT_NODE_XPATH.equals(xpath);
365     }
366
367     private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
368         cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
369                 Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
370         final Collection<DataNode> childDataNodeUpdates = dataNodeUpdate.getChildDataNodes();
371         for (final DataNode childDataNodeUpdate : childDataNodeUpdates) {
372             processDataNodeUpdate(anchor, childDataNodeUpdate);
373         }
374     }
375
376 }