2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2022 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2020-2022 Bell Canada.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.cps.spi.impl;
25 import com.google.common.collect.ImmutableSet;
26 import com.google.common.collect.ImmutableSet.Builder;
27 import com.hazelcast.map.IMap;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
36 import java.util.concurrent.TimeUnit;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 import java.util.stream.Collectors;
40 import javax.transaction.Transactional;
41 import lombok.RequiredArgsConstructor;
42 import lombok.extern.slf4j.Slf4j;
43 import org.hibernate.StaleStateException;
44 import org.onap.cps.cpspath.parser.CpsPathQuery;
45 import org.onap.cps.cpspath.parser.CpsPathUtil;
46 import org.onap.cps.cpspath.parser.PathParsingException;
47 import org.onap.cps.spi.CpsDataPersistenceService;
48 import org.onap.cps.spi.FetchDescendantsOption;
49 import org.onap.cps.spi.cache.AnchorDataCacheConfig;
50 import org.onap.cps.spi.cache.AnchorDataCacheEntry;
51 import org.onap.cps.spi.entities.AnchorEntity;
52 import org.onap.cps.spi.entities.DataspaceEntity;
53 import org.onap.cps.spi.entities.FragmentEntity;
54 import org.onap.cps.spi.entities.SchemaSetEntity;
55 import org.onap.cps.spi.entities.YangResourceEntity;
56 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
57 import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
58 import org.onap.cps.spi.exceptions.ConcurrencyException;
59 import org.onap.cps.spi.exceptions.CpsAdminException;
60 import org.onap.cps.spi.exceptions.CpsPathException;
61 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
62 import org.onap.cps.spi.model.DataNode;
63 import org.onap.cps.spi.model.DataNodeBuilder;
64 import org.onap.cps.spi.repository.AnchorRepository;
65 import org.onap.cps.spi.repository.DataspaceRepository;
66 import org.onap.cps.spi.repository.FragmentRepository;
67 import org.onap.cps.spi.utils.SessionManager;
68 import org.onap.cps.utils.JsonObjectMapper;
69 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder;
70 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
71 import org.springframework.dao.DataIntegrityViolationException;
72 import org.springframework.stereotype.Service;
76 @RequiredArgsConstructor
77 public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
79 private final DataspaceRepository dataspaceRepository;
80 private final AnchorRepository anchorRepository;
81 private final FragmentRepository fragmentRepository;
82 private final JsonObjectMapper jsonObjectMapper;
83 private final SessionManager sessionManager;
84 private final IMap<String, AnchorDataCacheEntry> anchorDataCache;
86 private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
87 private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE =
88 Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
89 private static final String TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME = "topLevelModulePrefix";
92 public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
93 final DataNode newChildDataNode) {
94 addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
98 public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
99 final Collection<DataNode> newListElements) {
100 addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
104 public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
105 final Collection<Collection<DataNode>> newLists) {
106 final Collection<String> failedXpaths = new HashSet<>();
107 newLists.forEach(newList -> {
109 addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
110 } catch (final AlreadyDefinedExceptionBatch e) {
111 failedXpaths.addAll(e.getAlreadyDefinedXpaths());
115 if (!failedXpaths.isEmpty()) {
116 throw new AlreadyDefinedExceptionBatch(failedXpaths);
121 private void addNewChildDataNode(final String dataspaceName, final String anchorName,
122 final String parentNodeXpath, final DataNode newChild) {
123 final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
124 final FragmentEntity newChildAsFragmentEntity =
125 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
126 parentFragmentEntity.getAnchor(), newChild);
127 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
129 fragmentRepository.save(newChildAsFragmentEntity);
130 } catch (final DataIntegrityViolationException e) {
131 throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
136 private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
137 final Collection<DataNode> newChildren) {
138 final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
139 final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
141 newChildren.forEach(newChildAsDataNode -> {
142 final FragmentEntity newChildAsFragmentEntity =
143 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
144 parentFragmentEntity.getAnchor(), newChildAsDataNode);
145 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
146 fragmentEntities.add(newChildAsFragmentEntity);
148 fragmentRepository.saveAll(fragmentEntities);
149 } catch (final DataIntegrityViolationException e) {
150 log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
151 e, fragmentEntities.size());
152 retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
156 private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
157 final String parentNodeXpath,
158 final Collection<DataNode> newChildren) {
159 final Collection<String> failedXpaths = new HashSet<>();
160 for (final DataNode newChild : newChildren) {
162 addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild);
163 } catch (final AlreadyDefinedException e) {
164 failedXpaths.add(newChild.getXpath());
167 if (!failedXpaths.isEmpty()) {
168 throw new AlreadyDefinedExceptionBatch(failedXpaths);
173 public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
174 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
175 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
176 final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
179 fragmentRepository.save(fragmentEntity);
180 } catch (final DataIntegrityViolationException exception) {
181 throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception);
186 * Convert DataNode object into Fragment and places the result in the fragments placeholder. Performs same action
187 * for all DataNode children recursively.
189 * @param dataspaceEntity dataspace
190 * @param anchorEntity anchorEntity
191 * @param dataNodeToBeConverted dataNode
192 * @return a Fragment built from current DataNode
194 private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
195 final AnchorEntity anchorEntity,
196 final DataNode dataNodeToBeConverted) {
197 final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
198 final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
199 for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
200 final FragmentEntity childFragment =
201 convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(),
203 childFragmentsImmutableSetBuilder.add(childFragment);
205 parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
206 return parentFragment;
209 private FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
210 final AnchorEntity anchorEntity, final DataNode dataNode) {
211 return FragmentEntity.builder()
212 .dataspace(dataspaceEntity)
213 .anchor(anchorEntity)
214 .xpath(dataNode.getXpath())
215 .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves()))
220 public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
221 final FetchDescendantsOption fetchDescendantsOption) {
222 final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
223 final DataNode dataNode = toDataNode(fragmentEntity, fetchDescendantsOption);
224 dataNode.setModuleNamePrefix(getRootModuleNamePrefix(fragmentEntity.getAnchor()));
228 private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
229 final String xpath) {
230 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
231 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
232 if (isRootXpath(xpath)) {
233 return fragmentRepository.findFirstRootByDataspaceAndAnchor(
234 dataspaceEntity, anchorEntity);
236 final String normalizedXpath;
238 normalizedXpath = CpsPathUtil.getNormalizedXpath(xpath);
239 } catch (final PathParsingException e) {
240 throw new CpsPathException(e.getMessage());
243 return fragmentRepository.getByDataspaceAndAnchorAndXpath(
244 dataspaceEntity, anchorEntity, normalizedXpath);
249 public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
250 final FetchDescendantsOption fetchDescendantsOption) {
251 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
252 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
253 final CpsPathQuery cpsPathQuery;
255 cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
256 } catch (final PathParsingException e) {
257 throw new CpsPathException(e.getMessage());
259 List<FragmentEntity> fragmentEntities =
260 fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
261 if (cpsPathQuery.hasAncestorAxis()) {
262 final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
263 fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList()
264 : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
266 return fragmentEntities.stream()
267 .map(fragmentEntity -> {
268 final DataNode dataNode = toDataNode(fragmentEntity, fetchDescendantsOption);
269 dataNode.setModuleNamePrefix(getRootModuleNamePrefix(anchorEntity));
271 }).collect(Collectors.toUnmodifiableList());
275 public String startSession() {
276 return sessionManager.startSession();
280 public void closeSession(final String sessionId) {
281 sessionManager.closeSession(sessionId, SessionManager.WITH_COMMIT);
285 public void lockAnchor(final String sessionId, final String dataspaceName,
286 final String anchorName, final Long timeoutInMilliseconds) {
287 sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds);
290 private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
291 final CpsPathQuery cpsPathQuery) {
292 final Set<String> ancestorXpath = new HashSet<>();
293 final Pattern pattern =
294 Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
295 + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*");
296 for (final FragmentEntity fragmentEntity : fragmentEntities) {
297 final Matcher matcher = pattern.matcher(fragmentEntity.getXpath());
298 if (matcher.matches()) {
299 ancestorXpath.add(matcher.group(1));
302 return ancestorXpath;
305 private DataNode toDataNode(final FragmentEntity fragmentEntity,
306 final FetchDescendantsOption fetchDescendantsOption) {
307 final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
308 Map<String, Object> leaves = new HashMap<>();
309 if (fragmentEntity.getAttributes() != null) {
310 leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
312 return new DataNodeBuilder()
313 .withXpath(fragmentEntity.getXpath())
315 .withChildDataNodes(childDataNodes).build();
318 private String getRootModuleNamePrefix(final AnchorEntity anchorEntity) {
319 final String cachedModuleNamePrefix = getModuleNamePrefixFromCache(anchorEntity);
320 if (cachedModuleNamePrefix != null) {
321 return cachedModuleNamePrefix;
323 final String moduleNamePrefix = buildSchemaContextAndRetrieveModulePrefix(anchorEntity);
324 cacheModuleNamePrefix(anchorEntity.getName(), moduleNamePrefix);
325 return moduleNamePrefix;
328 private String buildSchemaContextAndRetrieveModulePrefix(final AnchorEntity anchorEntity) {
329 final SchemaSetEntity schemaSetEntity = anchorEntity.getSchemaSet();
330 final Map<String, String> yangResourceNameToContent =
331 schemaSetEntity.getYangResources().stream().collect(
332 Collectors.toMap(YangResourceEntity::getFileName, YangResourceEntity::getContent));
333 final SchemaContext schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent)
335 return schemaContext.getModules().iterator().next().getName();
338 private void cacheModuleNamePrefix(final String anchorName, final String moduleNamePrefix) {
339 final AnchorDataCacheEntry anchorDataCacheEntry = new AnchorDataCacheEntry();
340 anchorDataCacheEntry.setProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME, moduleNamePrefix);
341 if (anchorDataCache.putIfAbsent(anchorName, anchorDataCacheEntry,
342 AnchorDataCacheConfig.ANCHOR_DATA_CACHE_TTL_SECS, TimeUnit.SECONDS) == null) {
343 log.debug("Module name prefix for an anchor {} is cached", anchorName);
347 private String getModuleNamePrefixFromCache(final AnchorEntity anchorEntity) {
348 if (anchorDataCache.containsKey(anchorEntity.getName())) {
349 final AnchorDataCacheEntry anchorDataCacheEntry = anchorDataCache.get(anchorEntity.getName());
350 return anchorDataCacheEntry.hasProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME)
351 ? anchorDataCacheEntry.getProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME).toString() : null;
356 private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
357 final FetchDescendantsOption fetchDescendantsOption) {
358 if (fetchDescendantsOption.hasNext()) {
359 return fragmentEntity.getChildFragments().stream()
360 .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next()))
361 .collect(Collectors.toList());
363 return Collections.emptyList();
367 public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
368 final Map<String, Object> leaves) {
369 final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
370 fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(leaves));
371 fragmentRepository.save(fragmentEntity);
375 public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
376 final DataNode dataNode) {
377 final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath());
378 updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
380 fragmentRepository.save(fragmentEntity);
381 } catch (final StaleStateException staleStateException) {
382 throw new ConcurrencyException("Concurrent Transactions",
383 String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
384 dataspaceName, anchorName, dataNode.getXpath()));
389 public void updateDataNodesAndDescendants(final String dataspaceName,
390 final String anchorName,
391 final List<DataNode> dataNodes) {
393 final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
394 .collect(Collectors.toMap(
395 dataNode -> dataNode,
396 dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath())));
397 dataNodeFragmentEntityMap.forEach(
398 (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode));
400 fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
401 } catch (final StaleStateException staleStateException) {
402 retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values());
406 private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
407 final Collection<FragmentEntity> fragmentEntities) {
408 final Collection<String> failedXpaths = new HashSet<>();
410 fragmentEntities.forEach(dataNodeFragment -> {
412 fragmentRepository.save(dataNodeFragment);
413 } catch (final StaleStateException e) {
414 failedXpaths.add(dataNodeFragment.getXpath());
418 if (!failedXpaths.isEmpty()) {
419 final String failedXpathsConcatenated = String.join(",", failedXpaths);
420 throw new ConcurrencyException("Concurrent Transactions", String.format(
421 "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.",
422 failedXpathsConcatenated, dataspaceName, anchorName));
426 private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity,
427 final DataNode newDataNode) {
429 existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
431 final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream()
432 .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
434 final Collection<FragmentEntity> updatedChildFragments = new HashSet<>();
436 for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) {
437 final FragmentEntity childFragment;
438 if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) {
439 childFragment = convertToFragmentWithAllDescendants(
440 existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild);
442 childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath());
443 updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild);
445 updatedChildFragments.add(childFragment);
448 existingFragmentEntity.getChildFragments().clear();
449 existingFragmentEntity.getChildFragments().addAll(updatedChildFragments);
454 public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
455 final Collection<DataNode> newListElements) {
456 final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
457 final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
458 final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
459 extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
460 deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
461 final Set<FragmentEntity> updatedChildFragmentEntities = new HashSet<>();
462 for (final DataNode newListElement : newListElements) {
463 final FragmentEntity existingListElementEntity =
464 existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath());
465 final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement,
466 existingListElementEntity);
468 updatedChildFragmentEntities.add(entityToBeAdded);
470 parentEntity.getChildFragments().addAll(updatedChildFragmentEntities);
471 fragmentRepository.save(parentEntity);
476 public void deleteDataNodes(final String dataspaceName, final String anchorName) {
477 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
478 anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName)
480 anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity)));
485 public void deleteListDataNode(final String dataspaceName, final String anchorName,
486 final String targetXpath) {
487 deleteDataNode(dataspaceName, anchorName, targetXpath, true);
492 public void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) {
493 deleteDataNode(dataspaceName, anchorName, targetXpath, false);
496 private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath,
497 final boolean onlySupportListNodeDeletion) {
498 final String parentNodeXpath;
499 FragmentEntity parentFragmentEntity = null;
500 boolean targetDeleted = false;
501 if (isRootXpath(targetXpath)) {
502 deleteDataNodes(dataspaceName, anchorName);
503 targetDeleted = true;
505 if (isRootContainerNodeXpath(targetXpath)) {
506 parentNodeXpath = targetXpath;
508 parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/'));
510 parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
511 final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/'));
512 final boolean isListElement = REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE
513 .matcher(lastXpathElement).find();
515 targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
517 targetDeleted = deleteAllListElements(parentFragmentEntity, targetXpath);
518 final boolean tryToDeleteDataNode = !targetDeleted && !onlySupportListNodeDeletion;
519 if (tryToDeleteDataNode) {
520 targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
524 if (!targetDeleted) {
525 final String additionalInformation = onlySupportListNodeDeletion
526 ? "The target is probably not a List." : "";
527 throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(),
528 parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation);
532 private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) {
533 final String normalizedTargetXpath = CpsPathUtil.getNormalizedXpath(targetXpath);
534 if (parentFragmentEntity.getXpath().equals(normalizedTargetXpath)) {
535 fragmentRepository.delete(parentFragmentEntity);
538 if (parentFragmentEntity.getChildFragments()
539 .removeIf(fragment -> fragment.getXpath().equals(normalizedTargetXpath))) {
540 fragmentRepository.save(parentFragmentEntity);
546 private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) {
547 final String normalizedListXpath = CpsPathUtil.getNormalizedXpath(listXpath);
548 final String deleteTargetXpathPrefix = normalizedListXpath + "[";
549 if (parentFragmentEntity.getChildFragments()
550 .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) {
551 fragmentRepository.save(parentFragmentEntity);
557 private static void deleteListElements(
558 final Collection<FragmentEntity> fragmentEntities,
559 final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath) {
560 fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values());
563 private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
564 if (newListElements.isEmpty()) {
565 throw new CpsAdminException("Invalid list replacement",
566 "Cannot replace list elements with empty collection");
568 final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
569 return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1);
572 private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
573 final DataNode newListElement,
574 final FragmentEntity existingListElementEntity) {
575 if (existingListElementEntity == null) {
576 return convertToFragmentWithAllDescendants(
577 parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement);
579 if (newListElement.getChildDataNodes().isEmpty()) {
580 copyAttributesFromNewListElement(existingListElementEntity, newListElement);
581 existingListElementEntity.getChildFragments().clear();
583 updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement);
585 return existingListElementEntity;
588 private static boolean isNewDataNode(final DataNode replacementDataNode,
589 final Map<String, FragmentEntity> existingListElementsByXpath) {
590 return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
593 private static boolean isRootContainerNodeXpath(final String xpath) {
594 return 0 == xpath.lastIndexOf('/');
597 private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
598 final DataNode newListElement) {
599 final FragmentEntity replacementFragmentEntity =
600 FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString(
601 newListElement.getLeaves())).build();
602 existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
605 private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
606 final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
607 return childEntities.stream()
608 .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix))
609 .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
612 private static boolean isRootXpath(final String xpath) {
613 return "/".equals(xpath) || "".equals(xpath);