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-2024 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.events.CpsDataUpdateEventsService;
43 import org.onap.cps.events.model.Data.Operation;
44 import org.onap.cps.spi.CpsDataPersistenceService;
45 import org.onap.cps.spi.FetchDescendantsOption;
46 import org.onap.cps.spi.exceptions.DataValidationException;
47 import org.onap.cps.spi.model.Anchor;
48 import org.onap.cps.spi.model.DataNode;
49 import org.onap.cps.spi.model.DataNodeBuilder;
50 import org.onap.cps.spi.model.DeltaReport;
51 import org.onap.cps.spi.utils.CpsValidator;
52 import org.onap.cps.utils.ContentType;
53 import org.onap.cps.utils.YangParser;
54 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
55 import org.springframework.stereotype.Service;
59 @RequiredArgsConstructor
60 public class CpsDataServiceImpl implements CpsDataService {
62 private static final String ROOT_NODE_XPATH = "/";
63 private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
65 private final CpsDataPersistenceService cpsDataPersistenceService;
66 private final CpsDataUpdateEventsService cpsDataUpdateEventsService;
67 private final CpsAnchorService cpsAnchorService;
69 private final CpsValidator cpsValidator;
70 private final YangParser yangParser;
71 private final CpsDeltaService cpsDeltaService;
74 public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
75 final OffsetDateTime observedTimestamp) {
76 saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON);
80 @Timed(value = "cps.data.service.datanode.root.save",
81 description = "Time taken to save a root data node")
82 public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
83 final OffsetDateTime observedTimestamp, final ContentType contentType) {
84 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
85 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
86 final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType);
87 cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
88 sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.CREATE, observedTimestamp);
92 public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
93 final String nodeData, final OffsetDateTime observedTimestamp) {
94 saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON);
98 @Timed(value = "cps.data.service.datanode.child.save",
99 description = "Time taken to save a child data node")
100 public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
101 final String nodeData, final OffsetDateTime observedTimestamp,
102 final ContentType contentType) {
103 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
104 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
105 final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType);
106 cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
107 sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp);
111 @Timed(value = "cps.data.service.list.element.save",
112 description = "Time taken to save list elements")
113 public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
114 final String jsonData, final OffsetDateTime observedTimestamp) {
115 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
116 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
117 final Collection<DataNode> listElementDataNodeCollection =
118 buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
119 if (isRootNodeXpath(parentNodeXpath)) {
120 cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
122 cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
123 listElementDataNodeCollection);
125 sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
129 @Timed(value = "cps.data.service.datanode.get",
130 description = "Time taken to get data nodes for an xpath")
131 public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
133 final FetchDescendantsOption fetchDescendantsOption) {
134 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
135 return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
139 @Timed(value = "cps.data.service.datanode.batch.get",
140 description = "Time taken to get a batch of data nodes")
141 public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
142 final Collection<String> xpaths,
143 final FetchDescendantsOption fetchDescendantsOption) {
144 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
145 return cpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, xpaths,
146 fetchDescendantsOption);
150 @Timed(value = "cps.data.service.datanode.leaves.update",
151 description = "Time taken to update a batch of leaf data nodes")
152 public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
153 final String jsonData, final OffsetDateTime observedTimestamp) {
154 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
155 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
156 final Collection<DataNode> dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData,
158 final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
159 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
160 cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
161 sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
165 @Timed(value = "cps.data.service.datanode.leaves.descendants.leaves.update",
166 description = "Time taken to update data node leaves and existing descendants leaves")
167 public void updateNodeLeavesAndExistingDescendantLeaves(final String dataspaceName, final String anchorName,
168 final String parentNodeXpath,
169 final String dataNodeUpdatesAsJson,
170 final OffsetDateTime observedTimestamp) {
171 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
172 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
173 final Collection<DataNode> dataNodeUpdates =
174 buildDataNodes(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
175 for (final DataNode dataNodeUpdate : dataNodeUpdates) {
176 processDataNodeUpdate(anchor, dataNodeUpdate);
178 sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
182 public String startSession() {
183 return cpsDataPersistenceService.startSession();
187 public void closeSession(final String sessionId) {
188 cpsDataPersistenceService.closeSession(sessionId);
192 public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
193 lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
197 public void lockAnchor(final String sessionID, final String dataspaceName,
198 final String anchorName, final Long timeoutInMilliseconds) {
199 cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
203 @Timed(value = "cps.data.service.get.delta",
204 description = "Time taken to get delta between anchors")
205 public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
206 final String sourceAnchorName,
207 final String targetAnchorName, final String xpath,
208 final FetchDescendantsOption fetchDescendantsOption) {
210 final Collection<DataNode> sourceDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
211 sourceAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
212 final Collection<DataNode> targetDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
213 targetAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
215 return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
219 @Timed(value = "cps.data.service.datanode.descendants.update",
220 description = "Time taken to update a data node and descendants")
221 public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
222 final String parentNodeXpath, final String jsonData,
223 final OffsetDateTime observedTimestamp) {
224 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
225 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
226 final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
227 cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
228 sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
232 @Timed(value = "cps.data.service.datanode.descendants.batch.update",
233 description = "Time taken to update a batch of data nodes and descendants")
234 public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
235 final Map<String, String> nodesJsonData,
236 final OffsetDateTime observedTimestamp) {
237 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
238 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
239 final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData);
240 cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
241 nodesJsonData.keySet().forEach(nodeXpath ->
242 sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp));
246 @Timed(value = "cps.data.service.list.update",
247 description = "Time taken to update a list")
248 public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
249 final String jsonData, final OffsetDateTime observedTimestamp) {
250 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
251 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
252 final Collection<DataNode> newListElements =
253 buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
254 replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
258 @Timed(value = "cps.data.service.list.batch.update",
259 description = "Time taken to update a batch of lists")
260 public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
261 final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
262 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
263 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
264 cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
265 sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
269 @Timed(value = "cps.data.service.datanode.delete",
270 description = "Time taken to delete a datanode")
271 public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
272 final OffsetDateTime observedTimestamp) {
273 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
274 cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
275 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
276 sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp);
280 @Timed(value = "cps.data.service.datanode.batch.delete",
281 description = "Time taken to delete a batch of datanodes")
282 public void deleteDataNodes(final String dataspaceName, final String anchorName,
283 final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
284 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
285 cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths);
286 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
287 dataNodeXpaths.forEach(dataNodeXpath ->
288 sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp));
293 @Timed(value = "cps.data.service.datanode.delete.anchor",
294 description = "Time taken to delete all datanodes for an anchor")
295 public void deleteDataNodes(final String dataspaceName, final String anchorName,
296 final OffsetDateTime observedTimestamp) {
297 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
298 cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
299 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
300 sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
304 @Timed(value = "cps.data.service.datanode.delete.anchor.batch",
305 description = "Time taken to delete all datanodes for multiple anchors")
306 public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames,
307 final OffsetDateTime observedTimestamp) {
308 cpsValidator.validateNameCharacters(dataspaceName);
309 cpsValidator.validateNameCharacters(anchorNames);
310 cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames);
311 for (final Anchor anchor : cpsAnchorService.getAnchors(dataspaceName, anchorNames)) {
312 sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
317 @Timed(value = "cps.data.service.list.delete",
318 description = "Time taken to delete a list or list element")
319 public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
320 final OffsetDateTime observedTimestamp) {
321 cpsValidator.validateNameCharacters(dataspaceName, anchorName);
322 cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
323 final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
324 sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp);
327 private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) {
328 final Collection<DataNode> dataNodes = new ArrayList<>();
329 for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
330 dataNodes.addAll(buildDataNodes(anchor, nodeJsonData.getKey(), nodeJsonData.getValue(), ContentType.JSON));
335 private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
336 final String nodeData, final ContentType contentType) {
338 if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
339 final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, "");
340 final Collection<DataNode> dataNodes = new DataNodeBuilder()
341 .withContainerNode(containerNode)
343 if (dataNodes.isEmpty()) {
344 throw new DataValidationException("No data nodes.", "No data nodes provided");
348 final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
349 final ContainerNode containerNode =
350 yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
351 final Collection<DataNode> dataNodes = new DataNodeBuilder()
352 .withParentNodeXpath(normalizedParentNodeXpath)
353 .withContainerNode(containerNode)
355 if (dataNodes.isEmpty()) {
356 throw new DataValidationException("No data nodes.", "No data nodes provided");
362 private static boolean isRootNodeXpath(final String xpath) {
363 return ROOT_NODE_XPATH.equals(xpath);
366 private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
367 cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
368 Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
369 final Collection<DataNode> childDataNodeUpdates = dataNodeUpdate.getChildDataNodes();
370 for (final DataNode childDataNodeUpdate : childDataNodeUpdates) {
371 processDataNodeUpdate(anchor, childDataNodeUpdate);
375 private void sendDataUpdatedEvent(final Anchor anchor, final String xpath,
376 final Operation operation, final OffsetDateTime observedTimestamp) {
378 cpsDataUpdateEventsService.publishCpsDataUpdateEvent(anchor, xpath, operation, observedTimestamp);
379 } catch (final Exception exception) {
380 log.error("Failed to send message to notification service", exception);