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.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 CpsAnchorService cpsAnchorService;
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 = cpsAnchorService.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 = cpsAnchorService.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 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);
118 cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
119 listElementDataNodeCollection);
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,
128 final FetchDescendantsOption fetchDescendantsOption) {
129 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
130 return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
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);
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,
153 final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
154 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
155 cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
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);
175 public String startSession() {
176 return cpsDataPersistenceService.startSession();
180 public void closeSession(final String sessionId) {
181 cpsDataPersistenceService.closeSession(sessionId);
185 public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
186 lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
190 public void lockAnchor(final String sessionID, final String dataspaceName,
191 final String anchorName, final Long timeoutInMilliseconds) {
192 cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
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) {
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);
208 return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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));
310 private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
311 final String nodeData, final ContentType contentType) {
312 final SchemaContext schemaContext = getSchemaContext(anchor);
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)
319 if (dataNodes.isEmpty()) {
320 throw new DataValidationException("No data nodes.", "No data nodes provided");
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)
331 if (dataNodes.isEmpty()) {
332 throw new DataValidationException("No data nodes.", "No data nodes provided");
337 private SchemaContext getSchemaContext(final Anchor anchor) {
338 return yangTextSchemaSourceSetCache
339 .get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext();
342 private static boolean isRootNodeXpath(final String xpath) {
343 return ROOT_NODE_XPATH.equals(xpath);
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);