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
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.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;
57 @RequiredArgsConstructor
58 public class CpsDataServiceImpl implements CpsDataService {
60 private static final String ROOT_NODE_XPATH = "/";
61 private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
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;
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);
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);
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);
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);
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);
116 cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
117 listElementDataNodeCollection);
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,
126 final FetchDescendantsOption fetchDescendantsOption) {
127 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
128 return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
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);
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,
151 final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
152 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
153 cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
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);
173 public String startSession() {
174 return cpsDataPersistenceService.startSession();
178 public void closeSession(final String sessionId) {
179 cpsDataPersistenceService.closeSession(sessionId);
183 public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
184 lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
188 public void lockAnchor(final String sessionID, final String dataspaceName,
189 final String anchorName, final Long timeoutInMilliseconds) {
190 cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
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) {
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);
206 return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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));
308 private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
309 final String nodeData, final ContentType contentType) {
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)
316 if (dataNodes.isEmpty()) {
317 throw new DataValidationException("No data nodes.", "No data nodes provided");
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)
328 if (dataNodes.isEmpty()) {
329 throw new DataValidationException("No data nodes.", "No data nodes provided");
335 private static boolean isRootNodeXpath(final String xpath) {
336 return ROOT_NODE_XPATH.equals(xpath);
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);