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 java.io.Serializable;
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.regex.Matcher;
37 import java.util.regex.Pattern;
38 import java.util.stream.Collectors;
39 import javax.transaction.Transactional;
40 import lombok.RequiredArgsConstructor;
41 import lombok.extern.slf4j.Slf4j;
42 import org.hibernate.StaleStateException;
43 import org.onap.cps.cpspath.parser.CpsPathQuery;
44 import org.onap.cps.cpspath.parser.CpsPathUtil;
45 import org.onap.cps.cpspath.parser.PathParsingException;
46 import org.onap.cps.spi.CpsDataPersistenceService;
47 import org.onap.cps.spi.FetchDescendantsOption;
48 import org.onap.cps.spi.entities.AnchorEntity;
49 import org.onap.cps.spi.entities.DataspaceEntity;
50 import org.onap.cps.spi.entities.FragmentEntity;
51 import org.onap.cps.spi.entities.FragmentEntityArranger;
52 import org.onap.cps.spi.entities.FragmentExtract;
53 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
54 import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
55 import org.onap.cps.spi.exceptions.ConcurrencyException;
56 import org.onap.cps.spi.exceptions.CpsAdminException;
57 import org.onap.cps.spi.exceptions.CpsPathException;
58 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
59 import org.onap.cps.spi.model.DataNode;
60 import org.onap.cps.spi.model.DataNodeBuilder;
61 import org.onap.cps.spi.repository.AnchorRepository;
62 import org.onap.cps.spi.repository.DataspaceRepository;
63 import org.onap.cps.spi.repository.FragmentRepository;
64 import org.onap.cps.spi.utils.SessionManager;
65 import org.onap.cps.utils.JsonObjectMapper;
66 import org.springframework.dao.DataIntegrityViolationException;
67 import org.springframework.stereotype.Service;
71 @RequiredArgsConstructor
72 public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
74 private final DataspaceRepository dataspaceRepository;
75 private final AnchorRepository anchorRepository;
76 private final FragmentRepository fragmentRepository;
77 private final JsonObjectMapper jsonObjectMapper;
78 private final SessionManager sessionManager;
80 private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
81 private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE =
82 Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
83 private static final String TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME = "topLevelModulePrefix";
86 public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
87 final DataNode newChildDataNode) {
88 addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
92 public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
93 final Collection<DataNode> newListElements) {
94 addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
98 public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
99 final Collection<Collection<DataNode>> newLists) {
100 final Collection<String> failedXpaths = new HashSet<>();
101 newLists.forEach(newList -> {
103 addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
104 } catch (final AlreadyDefinedExceptionBatch e) {
105 failedXpaths.addAll(e.getAlreadyDefinedXpaths());
109 if (!failedXpaths.isEmpty()) {
110 throw new AlreadyDefinedExceptionBatch(failedXpaths);
115 private void addNewChildDataNode(final String dataspaceName, final String anchorName,
116 final String parentNodeXpath, final DataNode newChild) {
117 final FragmentEntity parentFragmentEntity =
118 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
119 final FragmentEntity newChildAsFragmentEntity =
120 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
121 parentFragmentEntity.getAnchor(), newChild);
122 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
124 fragmentRepository.save(newChildAsFragmentEntity);
125 } catch (final DataIntegrityViolationException e) {
126 throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
131 private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
132 final Collection<DataNode> newChildren) {
133 final FragmentEntity parentFragmentEntity =
134 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
135 final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
137 newChildren.forEach(newChildAsDataNode -> {
138 final FragmentEntity newChildAsFragmentEntity =
139 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
140 parentFragmentEntity.getAnchor(), newChildAsDataNode);
141 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
142 fragmentEntities.add(newChildAsFragmentEntity);
144 fragmentRepository.saveAll(fragmentEntities);
145 } catch (final DataIntegrityViolationException e) {
146 log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
147 e, fragmentEntities.size());
148 retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
152 private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
153 final String parentNodeXpath,
154 final Collection<DataNode> newChildren) {
155 final Collection<String> failedXpaths = new HashSet<>();
156 for (final DataNode newChild : newChildren) {
158 addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild);
159 } catch (final AlreadyDefinedException e) {
160 failedXpaths.add(newChild.getXpath());
163 if (!failedXpaths.isEmpty()) {
164 throw new AlreadyDefinedExceptionBatch(failedXpaths);
169 public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
170 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
171 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
172 final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
175 fragmentRepository.save(fragmentEntity);
176 } catch (final DataIntegrityViolationException exception) {
177 throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception);
182 * Convert DataNode object into Fragment and places the result in the fragments placeholder. Performs same action
183 * for all DataNode children recursively.
185 * @param dataspaceEntity dataspace
186 * @param anchorEntity anchorEntity
187 * @param dataNodeToBeConverted dataNode
188 * @return a Fragment built from current DataNode
190 private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
191 final AnchorEntity anchorEntity,
192 final DataNode dataNodeToBeConverted) {
193 final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
194 final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
195 for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
196 final FragmentEntity childFragment =
197 convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(),
199 childFragmentsImmutableSetBuilder.add(childFragment);
201 parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
202 return parentFragment;
205 private FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
206 final AnchorEntity anchorEntity, final DataNode dataNode) {
207 return FragmentEntity.builder()
208 .dataspace(dataspaceEntity)
209 .anchor(anchorEntity)
210 .xpath(dataNode.getXpath())
211 .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves()))
216 public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
217 final FetchDescendantsOption fetchDescendantsOption) {
218 final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath,
219 fetchDescendantsOption);
220 return toDataNode(fragmentEntity, fetchDescendantsOption);
223 private FragmentEntity getFragmentWithoutDescendantsByXpath(final String dataspaceName,
224 final String anchorName,
225 final String xpath) {
226 return getFragmentByXpath(dataspaceName, anchorName, xpath, FetchDescendantsOption.OMIT_DESCENDANTS);
229 private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
230 final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
231 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
232 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
233 if (isRootXpath(xpath)) {
234 final List<FragmentExtract> fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity,
236 return FragmentEntityArranger.toFragmentEntityTree(anchorEntity,
239 final String normalizedXpath = getNormalizedXpath(xpath);
240 final FragmentEntity fragmentEntity;
241 if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
243 fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
245 fragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity, normalizedXpath);
247 if (fragmentEntity == null) {
248 throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath);
250 return fragmentEntity;
254 private FragmentEntity buildFragmentEntityFromFragmentExtracts(final AnchorEntity anchorEntity,
255 final String normalizedXpath) {
256 final FragmentEntity fragmentEntity;
257 final List<FragmentExtract> fragmentExtracts =
258 fragmentRepository.findByAnchorIdAndParentXpath(anchorEntity.getId(), normalizedXpath);
259 log.debug("Fetched {} fragment entities by anchor {} and cps path {}.",
260 fragmentExtracts.size(), anchorEntity.getName(), normalizedXpath);
261 fragmentEntity = FragmentEntityArranger.toFragmentEntityTree(anchorEntity, fragmentExtracts);
262 return fragmentEntity;
266 public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
267 final FetchDescendantsOption fetchDescendantsOption) {
268 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
269 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
270 final CpsPathQuery cpsPathQuery;
272 cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
273 } catch (final PathParsingException e) {
274 throw new CpsPathException(e.getMessage());
276 List<FragmentEntity> fragmentEntities =
277 fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
278 if (cpsPathQuery.hasAncestorAxis()) {
279 final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
280 fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList()
281 : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
283 return createDataNodesFromFragmentEntities(fetchDescendantsOption, anchorEntity,
287 private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption,
288 final AnchorEntity anchorEntity,
289 final List<FragmentEntity> fragmentEntities) {
290 final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
291 for (final FragmentEntity proxiedFragmentEntity : fragmentEntities) {
292 final DataNode dataNode;
293 if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
294 dataNode = toDataNode(proxiedFragmentEntity, fetchDescendantsOption);
296 final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath());
297 final FragmentEntity unproxiedFragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity,
299 dataNode = toDataNode(unproxiedFragmentEntity, fetchDescendantsOption);
301 dataNodes.add(dataNode);
303 return Collections.unmodifiableList(dataNodes);
306 private static String getNormalizedXpath(final String xpathSource) {
307 final String normalizedXpath;
309 normalizedXpath = CpsPathUtil.getNormalizedXpath(xpathSource);
310 } catch (final PathParsingException e) {
311 throw new CpsPathException(e.getMessage());
313 return normalizedXpath;
317 public String startSession() {
318 return sessionManager.startSession();
322 public void closeSession(final String sessionId) {
323 sessionManager.closeSession(sessionId, SessionManager.WITH_COMMIT);
327 public void lockAnchor(final String sessionId, final String dataspaceName,
328 final String anchorName, final Long timeoutInMilliseconds) {
329 sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds);
332 private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
333 final CpsPathQuery cpsPathQuery) {
334 final Set<String> ancestorXpath = new HashSet<>();
335 final Pattern pattern =
336 Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
337 + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*");
338 for (final FragmentEntity fragmentEntity : fragmentEntities) {
339 final Matcher matcher = pattern.matcher(fragmentEntity.getXpath());
340 if (matcher.matches()) {
341 ancestorXpath.add(matcher.group(1));
344 return ancestorXpath;
347 private DataNode toDataNode(final FragmentEntity fragmentEntity,
348 final FetchDescendantsOption fetchDescendantsOption) {
349 final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
350 Map<String, Serializable> leaves = new HashMap<>();
351 if (fragmentEntity.getAttributes() != null) {
352 leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
354 return new DataNodeBuilder()
355 .withXpath(fragmentEntity.getXpath())
357 .withChildDataNodes(childDataNodes).build();
360 private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
361 final FetchDescendantsOption fetchDescendantsOption) {
362 if (fetchDescendantsOption.hasNext()) {
363 return fragmentEntity.getChildFragments().stream()
364 .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next()))
365 .collect(Collectors.toList());
367 return Collections.emptyList();
371 public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
372 final Map<String, Serializable> leaves) {
373 final FragmentEntity fragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, xpath);
374 fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(leaves));
375 fragmentRepository.save(fragmentEntity);
379 public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
380 final DataNode dataNode) {
381 final FragmentEntity fragmentEntity =
382 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath());
383 updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
385 fragmentRepository.save(fragmentEntity);
386 } catch (final StaleStateException staleStateException) {
387 throw new ConcurrencyException("Concurrent Transactions",
388 String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
389 dataspaceName, anchorName, dataNode.getXpath()));
394 public void updateDataNodesAndDescendants(final String dataspaceName,
395 final String anchorName,
396 final List<DataNode> dataNodes) {
398 final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
399 .collect(Collectors.toMap(
400 dataNode -> dataNode,
402 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath())));
403 dataNodeFragmentEntityMap.forEach(
404 (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode));
406 fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
407 } catch (final StaleStateException staleStateException) {
408 retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values());
412 private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
413 final Collection<FragmentEntity> fragmentEntities) {
414 final Collection<String> failedXpaths = new HashSet<>();
416 fragmentEntities.forEach(dataNodeFragment -> {
418 fragmentRepository.save(dataNodeFragment);
419 } catch (final StaleStateException e) {
420 failedXpaths.add(dataNodeFragment.getXpath());
424 if (!failedXpaths.isEmpty()) {
425 final String failedXpathsConcatenated = String.join(",", failedXpaths);
426 throw new ConcurrencyException("Concurrent Transactions", String.format(
427 "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.",
428 failedXpathsConcatenated, dataspaceName, anchorName));
432 private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity,
433 final DataNode newDataNode) {
435 existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
437 final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream()
438 .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
440 final Collection<FragmentEntity> updatedChildFragments = new HashSet<>();
442 for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) {
443 final FragmentEntity childFragment;
444 if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) {
445 childFragment = convertToFragmentWithAllDescendants(
446 existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild);
448 childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath());
449 updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild);
451 updatedChildFragments.add(childFragment);
454 existingFragmentEntity.getChildFragments().clear();
455 existingFragmentEntity.getChildFragments().addAll(updatedChildFragments);
460 public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
461 final Collection<DataNode> newListElements) {
462 final FragmentEntity parentEntity =
463 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
464 final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
465 final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
466 extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
467 deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
468 final Set<FragmentEntity> updatedChildFragmentEntities = new HashSet<>();
469 for (final DataNode newListElement : newListElements) {
470 final FragmentEntity existingListElementEntity =
471 existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath());
472 final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement,
473 existingListElementEntity);
475 updatedChildFragmentEntities.add(entityToBeAdded);
477 parentEntity.getChildFragments().addAll(updatedChildFragmentEntities);
478 fragmentRepository.save(parentEntity);
483 public void deleteDataNodes(final String dataspaceName, final String anchorName) {
484 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
485 anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName)
487 anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity)));
492 public void deleteListDataNode(final String dataspaceName, final String anchorName,
493 final String targetXpath) {
494 deleteDataNode(dataspaceName, anchorName, targetXpath, true);
499 public void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) {
500 deleteDataNode(dataspaceName, anchorName, targetXpath, false);
503 private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath,
504 final boolean onlySupportListNodeDeletion) {
505 final String parentNodeXpath;
506 FragmentEntity parentFragmentEntity = null;
507 boolean targetDeleted;
508 if (isRootXpath(targetXpath)) {
509 deleteDataNodes(dataspaceName, anchorName);
510 targetDeleted = true;
512 if (isRootContainerNodeXpath(targetXpath)) {
513 parentNodeXpath = targetXpath;
515 parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/'));
517 parentFragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
518 final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/'));
519 final boolean isListElement = REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE
520 .matcher(lastXpathElement).find();
522 targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
524 targetDeleted = deleteAllListElements(parentFragmentEntity, targetXpath);
525 final boolean tryToDeleteDataNode = !targetDeleted && !onlySupportListNodeDeletion;
526 if (tryToDeleteDataNode) {
527 targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
531 if (!targetDeleted) {
532 final String additionalInformation = onlySupportListNodeDeletion
533 ? "The target is probably not a List." : "";
534 throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(),
535 parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation);
539 private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) {
540 final String normalizedTargetXpath = CpsPathUtil.getNormalizedXpath(targetXpath);
541 if (parentFragmentEntity.getXpath().equals(normalizedTargetXpath)) {
542 fragmentRepository.delete(parentFragmentEntity);
545 if (parentFragmentEntity.getChildFragments()
546 .removeIf(fragment -> fragment.getXpath().equals(normalizedTargetXpath))) {
547 fragmentRepository.save(parentFragmentEntity);
553 private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) {
554 final String normalizedListXpath = CpsPathUtil.getNormalizedXpath(listXpath);
555 final String deleteTargetXpathPrefix = normalizedListXpath + "[";
556 if (parentFragmentEntity.getChildFragments()
557 .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) {
558 fragmentRepository.save(parentFragmentEntity);
564 private static void deleteListElements(
565 final Collection<FragmentEntity> fragmentEntities,
566 final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath) {
567 fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values());
570 private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
571 if (newListElements.isEmpty()) {
572 throw new CpsAdminException("Invalid list replacement",
573 "Cannot replace list elements with empty collection");
575 final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
576 return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1);
579 private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
580 final DataNode newListElement,
581 final FragmentEntity existingListElementEntity) {
582 if (existingListElementEntity == null) {
583 return convertToFragmentWithAllDescendants(
584 parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement);
586 if (newListElement.getChildDataNodes().isEmpty()) {
587 copyAttributesFromNewListElement(existingListElementEntity, newListElement);
588 existingListElementEntity.getChildFragments().clear();
590 updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement);
592 return existingListElementEntity;
595 private static boolean isNewDataNode(final DataNode replacementDataNode,
596 final Map<String, FragmentEntity> existingListElementsByXpath) {
597 return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
600 private static boolean isRootContainerNodeXpath(final String xpath) {
601 return 0 == xpath.lastIndexOf('/');
604 private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
605 final DataNode newListElement) {
606 final FragmentEntity replacementFragmentEntity =
607 FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString(
608 newListElement.getLeaves())).build();
609 existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
612 private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
613 final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
614 return childEntities.stream()
615 .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix))
616 .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
619 private static boolean isRootXpath(final String xpath) {
620 return "/".equals(xpath) || "".equals(xpath);