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
13 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 * SPDX-License-Identifier: Apache-2.0
22 * ============LICENSE_END=========================================================
25 package org.onap.cps.api.impl;
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;
35 import java.util.stream.Collectors;
36 import lombok.RequiredArgsConstructor;
37 import lombok.extern.slf4j.Slf4j;
38 import org.onap.cps.api.CpsAdminService;
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;
58 @RequiredArgsConstructor
59 public class CpsDataServiceImpl implements CpsDataService {
61 private static final String ROOT_NODE_XPATH = "/";
62 private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
64 private final CpsDataPersistenceService cpsDataPersistenceService;
65 private final CpsAdminService cpsAdminService;
66 private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache;
67 private final CpsValidator cpsValidator;
68 private final TimedYangParser timedYangParser;
69 private final CpsDeltaService cpsDeltaService;
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);
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 = cpsAdminService.getAnchor(dataspaceName, anchorName);
84 final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType);
85 cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
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);
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 = cpsAdminService.getAnchor(dataspaceName, anchorName);
102 final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType);
103 cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
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 = cpsAdminService.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);
118 cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
119 listElementDataNodeCollection);
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 = cpsAdminService.getAnchor(dataspaceName, anchorName);
130 final Collection<Collection<DataNode>> listElementDataNodeCollections =
131 buildDataNodes(anchor, parentNodeXpath, jsonDataList, ContentType.JSON);
132 cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath,
133 listElementDataNodeCollections);
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,
141 final FetchDescendantsOption fetchDescendantsOption) {
142 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
143 return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
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);
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 = cpsAdminService.getAnchor(dataspaceName, anchorName);
164 final Collection<DataNode> dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData,
166 final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
167 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
168 cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
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 = cpsAdminService.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);
188 public String startSession() {
189 return cpsDataPersistenceService.startSession();
193 public void closeSession(final String sessionId) {
194 cpsDataPersistenceService.closeSession(sessionId);
198 public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
199 lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
203 public void lockAnchor(final String sessionID, final String dataspaceName,
204 final String anchorName, final Long timeoutInMilliseconds) {
205 cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
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) {
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);
221 return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
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 = cpsAdminService.getAnchor(dataspaceName, anchorName);
232 final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
233 cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
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 = cpsAdminService.getAnchor(dataspaceName, anchorName);
244 final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData);
245 cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
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 = cpsAdminService.getAnchor(dataspaceName, anchorName);
255 final Collection<DataNode> newListElements =
256 buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
257 replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
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);
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);
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);
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);
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);
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);
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));
323 private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
324 final String nodeData, final ContentType contentType) {
325 final SchemaContext schemaContext = getSchemaContext(anchor);
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)
332 if (dataNodes.isEmpty()) {
333 throw new DataValidationException("No data nodes.", "No data nodes provided");
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)
344 if (dataNodes.isEmpty()) {
345 throw new DataValidationException("No data nodes.", "No data nodes provided");
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());
358 private SchemaContext getSchemaContext(final Anchor anchor) {
359 return yangTextSchemaSourceSetCache
360 .get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext();
363 private static boolean isRootNodeXpath(final String xpath) {
364 return ROOT_NODE_XPATH.equals(xpath);
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);