Addressed an adaptability issue
[cps.git] / cps-service / src / main / java / org / onap / cps / api / impl / CpsDataServiceImpl.java
1 /*
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
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.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.stream.Collectors;
37 import lombok.RequiredArgsConstructor;
38 import lombok.extern.slf4j.Slf4j;
39 import org.onap.cps.api.CpsAnchorService;
40 import org.onap.cps.api.CpsDataService;
41 import org.onap.cps.api.CpsDeltaService;
42 import org.onap.cps.cpspath.parser.CpsPathUtil;
43 import org.onap.cps.events.CpsDataUpdateEventsService;
44 import org.onap.cps.events.model.Data.Operation;
45 import org.onap.cps.spi.CpsDataPersistenceService;
46 import org.onap.cps.spi.FetchDescendantsOption;
47 import org.onap.cps.spi.exceptions.DataValidationException;
48 import org.onap.cps.spi.model.Anchor;
49 import org.onap.cps.spi.model.DataNode;
50 import org.onap.cps.spi.model.DataNodeBuilder;
51 import org.onap.cps.spi.model.DeltaReport;
52 import org.onap.cps.spi.utils.CpsValidator;
53 import org.onap.cps.utils.ContentType;
54 import org.onap.cps.utils.DataMapUtils;
55 import org.onap.cps.utils.JsonObjectMapper;
56 import org.onap.cps.utils.PrefixResolver;
57 import org.onap.cps.utils.YangParser;
58 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
59 import org.springframework.stereotype.Service;
60
61 @Service
62 @Slf4j
63 @RequiredArgsConstructor
64 public class CpsDataServiceImpl implements CpsDataService {
65
66     private static final String ROOT_NODE_XPATH = "/";
67     private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
68     private static final String NO_DATA_NODES = "No data nodes.";
69
70     private final CpsDataPersistenceService cpsDataPersistenceService;
71     private final CpsDataUpdateEventsService cpsDataUpdateEventsService;
72     private final CpsAnchorService cpsAnchorService;
73
74     private final CpsValidator cpsValidator;
75     private final YangParser yangParser;
76     private final CpsDeltaService cpsDeltaService;
77     private final JsonObjectMapper jsonObjectMapper;
78     private final PrefixResolver prefixResolver;
79
80     @Override
81     public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
82         final OffsetDateTime observedTimestamp) {
83         saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON);
84     }
85
86     @Override
87     @Timed(value = "cps.data.service.datanode.root.save",
88         description = "Time taken to save a root data node")
89     public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
90                          final OffsetDateTime observedTimestamp, final ContentType contentType) {
91         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
92         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
93         final Collection<DataNode> dataNodes =
94                 buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
95         cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
96         sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.CREATE, observedTimestamp);
97     }
98
99     @Override
100     public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
101                          final String nodeData, final OffsetDateTime observedTimestamp) {
102         saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON);
103     }
104
105     @Override
106     @Timed(value = "cps.data.service.datanode.child.save",
107         description = "Time taken to save a child data node")
108     public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
109                          final String nodeData, final OffsetDateTime observedTimestamp,
110                          final ContentType contentType) {
111         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
112         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
113         final Collection<DataNode> dataNodes =
114                 buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
115         cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
116         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp);
117     }
118
119     @Override
120     @Timed(value = "cps.data.service.list.element.save",
121         description = "Time taken to save list elements")
122     public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
123                                  final String jsonData, final OffsetDateTime observedTimestamp) {
124         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
125         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
126         final Collection<DataNode> listElementDataNodeCollection =
127             buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON);
128         if (isRootNodeXpath(parentNodeXpath)) {
129             cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
130         } else {
131             cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
132                                                       listElementDataNodeCollection);
133         }
134         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
135     }
136
137     @Override
138     @Timed(value = "cps.data.service.datanode.get",
139             description = "Time taken to get data nodes for an xpath")
140     public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
141                                              final String xpath,
142                                              final FetchDescendantsOption fetchDescendantsOption) {
143         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
144         return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
145     }
146
147     @Override
148     @Timed(value = "cps.data.service.datanode.batch.get",
149         description = "Time taken to get a batch of data nodes")
150     public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
151                                                               final Collection<String> xpaths,
152                                                               final FetchDescendantsOption fetchDescendantsOption) {
153         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
154         return cpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, xpaths,
155                 fetchDescendantsOption);
156     }
157
158     @Override
159     @Timed(value = "cps.data.service.datanode.leaves.update",
160         description = "Time taken to update a batch of leaf data nodes")
161     public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
162         final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) {
163         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
164         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
165         final Collection<DataNode> dataNodesInPatch =
166                 buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
167         final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
168                 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
169         cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
170         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
171     }
172
173     @Override
174     @Timed(value = "cps.data.service.datanode.leaves.descendants.leaves.update",
175         description = "Time taken to update data node leaves and existing descendants leaves")
176     public void updateNodeLeavesAndExistingDescendantLeaves(final String dataspaceName, final String anchorName,
177         final String parentNodeXpath,
178         final String dataNodeUpdatesAsJson,
179         final OffsetDateTime observedTimestamp) {
180         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
181         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
182         final Collection<DataNode> dataNodeUpdates =
183             buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
184         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
185             processDataNodeUpdate(anchor, dataNodeUpdate);
186         }
187         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
188     }
189
190     @Override
191     public String startSession() {
192         return cpsDataPersistenceService.startSession();
193     }
194
195     @Override
196     public void closeSession(final String sessionId) {
197         cpsDataPersistenceService.closeSession(sessionId);
198     }
199
200     @Override
201     public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
202         lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
203     }
204
205     @Override
206     public void lockAnchor(final String sessionID, final String dataspaceName,
207                            final String anchorName, final Long timeoutInMilliseconds) {
208         cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
209     }
210
211     @Override
212     @Timed(value = "cps.data.service.get.delta",
213             description = "Time taken to get delta between anchors")
214     public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
215                                                            final String sourceAnchorName,
216                                                            final String targetAnchorName, final String xpath,
217                                                            final FetchDescendantsOption fetchDescendantsOption) {
218
219         final Collection<DataNode> sourceDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
220                 sourceAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
221         final Collection<DataNode> targetDataNodes = getDataNodesForMultipleXpaths(dataspaceName,
222                 targetAnchorName, Collections.singletonList(xpath), fetchDescendantsOption);
223
224         return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
225     }
226
227     @Timed(value = "cps.data.service.get.deltaBetweenAnchorAndPayload",
228             description = "Time taken to get delta between anchor and a payload")
229     @Override
230     public List<DeltaReport> getDeltaByDataspaceAnchorAndPayload(final String dataspaceName,
231                                                                 final String sourceAnchorName, final String xpath,
232                                                                 final Map<String, String> yangResourcesNameToContentMap,
233                                                                 final String targetData,
234                                                                 final FetchDescendantsOption fetchDescendantsOption) {
235
236         final Anchor sourceAnchor = cpsAnchorService.getAnchor(dataspaceName, sourceAnchorName);
237
238         final Collection<DataNode> sourceDataNodes = getDataNodes(dataspaceName,
239                 sourceAnchorName, xpath, fetchDescendantsOption);
240
241         final Collection<DataNode> sourceDataNodesRebuilt =
242                 new ArrayList<>(rebuildSourceDataNodes(xpath, sourceAnchor, sourceDataNodes));
243
244         final Collection<DataNode> targetDataNodes =
245                 new ArrayList<>(buildTargetDataNodes(sourceAnchor, xpath, yangResourcesNameToContentMap, targetData));
246
247         return cpsDeltaService.getDeltaReports(sourceDataNodesRebuilt, targetDataNodes);
248     }
249
250     @Override
251     @Timed(value = "cps.data.service.datanode.descendants.update",
252         description = "Time taken to update a data node and descendants")
253     public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
254                                              final String parentNodeXpath, final String jsonData,
255                                              final OffsetDateTime observedTimestamp) {
256         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
257         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
258         final Collection<DataNode> dataNodes =
259                 buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON);
260         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
261         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
262     }
263
264     @Override
265     @Timed(value = "cps.data.service.datanode.descendants.batch.update",
266         description = "Time taken to update a batch of data nodes and descendants")
267     public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
268                                               final Map<String, String> nodesJsonData,
269                                               final OffsetDateTime observedTimestamp) {
270         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
271         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
272         final Collection<DataNode> dataNodes = buildDataNodesWithParentNodeXpath(anchor, nodesJsonData);
273         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
274         nodesJsonData.keySet().forEach(nodeXpath ->
275                 sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp));
276     }
277
278     @Override
279     @Timed(value = "cps.data.service.list.update",
280         description = "Time taken to update a list")
281     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
282             final String jsonData, final OffsetDateTime observedTimestamp) {
283         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
284         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
285         final Collection<DataNode> newListElements =
286             buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON);
287         replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
288     }
289
290     @Override
291     @Timed(value = "cps.data.service.list.batch.update",
292         description = "Time taken to update a batch of lists")
293     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
294             final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
295         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
296         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
297         cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
298         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
299     }
300
301     @Override
302     @Timed(value = "cps.data.service.datanode.delete",
303         description = "Time taken to delete a datanode")
304     public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
305                                final OffsetDateTime observedTimestamp) {
306         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
307         cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
308         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
309         sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp);
310     }
311
312     @Override
313     @Timed(value = "cps.data.service.datanode.batch.delete",
314         description = "Time taken to delete a batch of datanodes")
315     public void deleteDataNodes(final String dataspaceName, final String anchorName,
316                                 final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
317         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
318         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths);
319         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
320         dataNodeXpaths.forEach(dataNodeXpath ->
321                 sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp));
322     }
323
324
325     @Override
326     @Timed(value = "cps.data.service.datanode.delete.anchor",
327         description = "Time taken to delete all datanodes for an anchor")
328     public void deleteDataNodes(final String dataspaceName, final String anchorName,
329                                 final OffsetDateTime observedTimestamp) {
330         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
331         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
332         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
333         sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
334     }
335
336     @Override
337     @Timed(value = "cps.data.service.datanode.delete.anchor.batch",
338         description = "Time taken to delete all datanodes for multiple anchors")
339     public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames,
340                                 final OffsetDateTime observedTimestamp) {
341         cpsValidator.validateNameCharacters(dataspaceName);
342         cpsValidator.validateNameCharacters(anchorNames);
343         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames);
344         for (final Anchor anchor : cpsAnchorService.getAnchors(dataspaceName, anchorNames)) {
345             sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
346         }
347     }
348
349     @Override
350     @Timed(value = "cps.data.service.list.delete",
351         description = "Time taken to delete a list or list element")
352     public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
353         final OffsetDateTime observedTimestamp) {
354         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
355         cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
356         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
357         sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp);
358     }
359
360
361     private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor,
362                                                         final Collection<DataNode> sourceDataNodes) {
363
364         final Collection<DataNode> sourceDataNodesRebuilt = new ArrayList<>();
365         if (sourceDataNodes != null) {
366             final String sourceDataNodesAsJson = getDataNodesAsJson(sourceAnchor, sourceDataNodes);
367             sourceDataNodesRebuilt.addAll(
368                     buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
369         }
370         return sourceDataNodesRebuilt;
371     }
372
373     private Collection<DataNode> buildTargetDataNodes(final Anchor sourceAnchor, final String xpath,
374                                                       final Map<String, String> yangResourcesNameToContentMap,
375                                                       final String targetData) {
376         if (yangResourcesNameToContentMap.isEmpty()) {
377             return buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, targetData, ContentType.JSON);
378         } else {
379             return buildDataNodesWithYangResourceAndXpath(yangResourcesNameToContentMap, xpath,
380                     targetData, ContentType.JSON);
381         }
382     }
383
384     private String getDataNodesAsJson(final Anchor anchor, final Collection<DataNode> dataNodes) {
385
386         final List<Map<String, Object>> prefixToDataNodes = prefixResolver(anchor, dataNodes);
387         final Map<String, Object> targetDataAsJsonObject = getNodeDataAsJsonString(prefixToDataNodes);
388         return jsonObjectMapper.asJsonString(targetDataAsJsonObject);
389     }
390
391     private Map<String, Object> getNodeDataAsJsonString(final List<Map<String, Object>> prefixToDataNodes) {
392         final Map<String, Object>  nodeDataAsJson = new HashMap<>();
393         for (final Map<String, Object> prefixToDataNode : prefixToDataNodes) {
394             nodeDataAsJson.putAll(prefixToDataNode);
395         }
396         return nodeDataAsJson;
397     }
398
399     private List<Map<String, Object>> prefixResolver(final Anchor anchor, final Collection<DataNode> dataNodes) {
400         final List<Map<String, Object>> prefixToDataNodes = new ArrayList<>(dataNodes.size());
401         for (final DataNode dataNode: dataNodes) {
402             final String prefix = prefixResolver
403                     .getPrefix(anchor.getDataspaceName(), anchor.getName(), dataNode.getXpath());
404             final Map<String, Object> prefixToDataNode = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
405             prefixToDataNodes.add(prefixToDataNode);
406         }
407         return prefixToDataNodes;
408     }
409
410     private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor,
411                                                                    final Map<String, String> nodesJsonData) {
412         final Collection<DataNode> dataNodes = new ArrayList<>();
413         for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
414             dataNodes.addAll(buildDataNodesWithParentNodeXpath(anchor, nodeJsonData.getKey(),
415                     nodeJsonData.getValue(), ContentType.JSON));
416         }
417         return dataNodes;
418     }
419
420     private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor, final String parentNodeXpath,
421                                                                  final String nodeData, final ContentType contentType) {
422
423         if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
424             final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, "");
425             final Collection<DataNode> dataNodes = new DataNodeBuilder()
426                     .withContainerNode(containerNode)
427                     .buildCollection();
428             if (dataNodes.isEmpty()) {
429                 throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
430             }
431             return dataNodes;
432         }
433         final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
434         final ContainerNode containerNode =
435             yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
436         final Collection<DataNode> dataNodes = new DataNodeBuilder()
437             .withParentNodeXpath(normalizedParentNodeXpath)
438             .withContainerNode(containerNode)
439             .buildCollection();
440         if (dataNodes.isEmpty()) {
441             throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
442         }
443         return dataNodes;
444     }
445
446     private Collection<DataNode> buildDataNodesWithParentNodeXpath(
447                                           final Map<String, String> yangResourcesNameToContentMap, final String xpath,
448                                           final String nodeData, final ContentType contentType) {
449
450         if (isRootNodeXpath(xpath)) {
451             final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
452                     yangResourcesNameToContentMap, "");
453             final Collection<DataNode> dataNodes = new DataNodeBuilder()
454                     .withContainerNode(containerNode)
455                     .buildCollection();
456             if (dataNodes.isEmpty()) {
457                 throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
458             }
459             return dataNodes;
460         }
461         final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(xpath);
462         final ContainerNode containerNode =
463                 yangParser.parseData(contentType, nodeData, yangResourcesNameToContentMap, normalizedParentNodeXpath);
464         final Collection<DataNode> dataNodes = new DataNodeBuilder()
465                 .withParentNodeXpath(normalizedParentNodeXpath)
466                 .withContainerNode(containerNode)
467                 .buildCollection();
468         if (dataNodes.isEmpty()) {
469             throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
470         }
471         return dataNodes;
472     }
473
474     private Collection<DataNode> buildDataNodesWithAnchorAndXpath(final Anchor anchor, final String xpath,
475                                                                   final String nodeData,
476                                                                   final ContentType contentType) {
477
478         if (!isRootNodeXpath(xpath)) {
479             final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
480             if (parentNodeXpath.isEmpty()) {
481                 return buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
482             }
483             return buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
484         }
485         return buildDataNodesWithParentNodeXpath(anchor, xpath, nodeData, contentType);
486     }
487
488     private Collection<DataNode> buildDataNodesWithYangResourceAndXpath(
489                                             final Map<String, String> yangResourcesNameToContentMap, final String xpath,
490                                             final String nodeData, final ContentType contentType) {
491         if (!isRootNodeXpath(xpath)) {
492             final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
493             if (parentNodeXpath.isEmpty()) {
494                 return buildDataNodesWithParentNodeXpath(yangResourcesNameToContentMap, ROOT_NODE_XPATH,
495                         nodeData, contentType);
496             }
497             return buildDataNodesWithParentNodeXpath(yangResourcesNameToContentMap, parentNodeXpath,
498                     nodeData, contentType);
499         }
500         return buildDataNodesWithParentNodeXpath(yangResourcesNameToContentMap, xpath, nodeData, contentType);
501     }
502
503     private static boolean isRootNodeXpath(final String xpath) {
504         return ROOT_NODE_XPATH.equals(xpath);
505     }
506
507     private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
508         cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
509                 Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
510         final Collection<DataNode> childDataNodeUpdates = dataNodeUpdate.getChildDataNodes();
511         for (final DataNode childDataNodeUpdate : childDataNodeUpdates) {
512             processDataNodeUpdate(anchor, childDataNodeUpdate);
513         }
514     }
515
516     private void sendDataUpdatedEvent(final Anchor anchor, final String xpath,
517                                       final Operation operation, final OffsetDateTime observedTimestamp) {
518         try {
519             cpsDataUpdateEventsService.publishCpsDataUpdateEvent(anchor, xpath, operation, observedTimestamp);
520         } catch (final Exception exception) {
521             log.error("Failed to send message to notification service", exception);
522         }
523     }
524 }