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.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 import java.util.stream.Collectors;
38 import javax.transaction.Transactional;
39 import lombok.RequiredArgsConstructor;
40 import lombok.extern.slf4j.Slf4j;
41 import org.hibernate.StaleStateException;
42 import org.onap.cps.cpspath.parser.CpsPathQuery;
43 import org.onap.cps.cpspath.parser.CpsPathUtil;
44 import org.onap.cps.cpspath.parser.PathParsingException;
45 import org.onap.cps.spi.CpsDataPersistenceService;
46 import org.onap.cps.spi.FetchDescendantsOption;
47 import org.onap.cps.spi.entities.AnchorEntity;
48 import org.onap.cps.spi.entities.DataspaceEntity;
49 import org.onap.cps.spi.entities.FragmentEntity;
50 import org.onap.cps.spi.entities.FragmentEntityArranger;
51 import org.onap.cps.spi.entities.FragmentExtract;
52 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
53 import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
54 import org.onap.cps.spi.exceptions.ConcurrencyException;
55 import org.onap.cps.spi.exceptions.CpsAdminException;
56 import org.onap.cps.spi.exceptions.CpsPathException;
57 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
58 import org.onap.cps.spi.model.DataNode;
59 import org.onap.cps.spi.model.DataNodeBuilder;
60 import org.onap.cps.spi.repository.AnchorRepository;
61 import org.onap.cps.spi.repository.DataspaceRepository;
62 import org.onap.cps.spi.repository.FragmentRepository;
63 import org.onap.cps.spi.utils.SessionManager;
64 import org.onap.cps.utils.JsonObjectMapper;
65 import org.springframework.dao.DataIntegrityViolationException;
66 import org.springframework.stereotype.Service;
70 @RequiredArgsConstructor
71 public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
73 private final DataspaceRepository dataspaceRepository;
74 private final AnchorRepository anchorRepository;
75 private final FragmentRepository fragmentRepository;
76 private final JsonObjectMapper jsonObjectMapper;
77 private final SessionManager sessionManager;
79 private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
80 private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE =
81 Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
82 private static final String TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME = "topLevelModulePrefix";
85 public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
86 final DataNode newChildDataNode) {
87 addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
91 public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
92 final Collection<DataNode> newListElements) {
93 addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
97 public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
98 final Collection<Collection<DataNode>> newLists) {
99 final Collection<String> failedXpaths = new HashSet<>();
100 newLists.forEach(newList -> {
102 addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
103 } catch (final AlreadyDefinedExceptionBatch e) {
104 failedXpaths.addAll(e.getAlreadyDefinedXpaths());
108 if (!failedXpaths.isEmpty()) {
109 throw new AlreadyDefinedExceptionBatch(failedXpaths);
114 private void addNewChildDataNode(final String dataspaceName, final String anchorName,
115 final String parentNodeXpath, final DataNode newChild) {
116 final FragmentEntity parentFragmentEntity =
117 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
118 final FragmentEntity newChildAsFragmentEntity =
119 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
120 parentFragmentEntity.getAnchor(), newChild);
121 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
123 fragmentRepository.save(newChildAsFragmentEntity);
124 } catch (final DataIntegrityViolationException e) {
125 throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
130 private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
131 final Collection<DataNode> newChildren) {
132 final FragmentEntity parentFragmentEntity =
133 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
134 final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
136 newChildren.forEach(newChildAsDataNode -> {
137 final FragmentEntity newChildAsFragmentEntity =
138 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
139 parentFragmentEntity.getAnchor(), newChildAsDataNode);
140 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
141 fragmentEntities.add(newChildAsFragmentEntity);
143 fragmentRepository.saveAll(fragmentEntities);
144 } catch (final DataIntegrityViolationException e) {
145 log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
146 e, fragmentEntities.size());
147 retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
151 private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
152 final String parentNodeXpath,
153 final Collection<DataNode> newChildren) {
154 final Collection<String> failedXpaths = new HashSet<>();
155 for (final DataNode newChild : newChildren) {
157 addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild);
158 } catch (final AlreadyDefinedException e) {
159 failedXpaths.add(newChild.getXpath());
162 if (!failedXpaths.isEmpty()) {
163 throw new AlreadyDefinedExceptionBatch(failedXpaths);
168 public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
169 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
170 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
171 final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
174 fragmentRepository.save(fragmentEntity);
175 } catch (final DataIntegrityViolationException exception) {
176 throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception);
181 * Convert DataNode object into Fragment and places the result in the fragments placeholder. Performs same action
182 * for all DataNode children recursively.
184 * @param dataspaceEntity dataspace
185 * @param anchorEntity anchorEntity
186 * @param dataNodeToBeConverted dataNode
187 * @return a Fragment built from current DataNode
189 private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
190 final AnchorEntity anchorEntity,
191 final DataNode dataNodeToBeConverted) {
192 final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
193 final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
194 for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
195 final FragmentEntity childFragment =
196 convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(),
198 childFragmentsImmutableSetBuilder.add(childFragment);
200 parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
201 return parentFragment;
204 private FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
205 final AnchorEntity anchorEntity, final DataNode dataNode) {
206 return FragmentEntity.builder()
207 .dataspace(dataspaceEntity)
208 .anchor(anchorEntity)
209 .xpath(dataNode.getXpath())
210 .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves()))
215 public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
216 final FetchDescendantsOption fetchDescendantsOption) {
217 final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath,
218 fetchDescendantsOption);
219 return toDataNode(fragmentEntity, fetchDescendantsOption);
222 private FragmentEntity getFragmentWithoutDescendantsByXpath(final String dataspaceName,
223 final String anchorName,
224 final String xpath) {
225 return getFragmentByXpath(dataspaceName, anchorName, xpath, FetchDescendantsOption.OMIT_DESCENDANTS);
228 private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
229 final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
230 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
231 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
232 if (isRootXpath(xpath)) {
233 final List<FragmentExtract> fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity,
235 return FragmentEntityArranger.toFragmentEntityTree(anchorEntity,
238 final String normalizedXpath = getNormalizedXpath(xpath);
239 final FragmentEntity fragmentEntity;
240 if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
242 fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
244 fragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity, normalizedXpath);
246 if (fragmentEntity == null) {
247 throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath);
249 return fragmentEntity;
253 private FragmentEntity buildFragmentEntityFromFragmentExtracts(final AnchorEntity anchorEntity,
254 final String normalizedXpath) {
255 final FragmentEntity fragmentEntity;
256 final List<FragmentExtract> fragmentExtracts =
257 fragmentRepository.findByAnchorIdAndParentXpath(anchorEntity.getId(), normalizedXpath);
258 log.debug("Fetched {} fragment entities by anchor {} and cps path {}.",
259 fragmentExtracts.size(), anchorEntity.getName(), normalizedXpath);
260 fragmentEntity = FragmentEntityArranger.toFragmentEntityTree(anchorEntity, fragmentExtracts);
261 return fragmentEntity;
265 public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
266 final FetchDescendantsOption fetchDescendantsOption) {
267 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
268 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
269 final CpsPathQuery cpsPathQuery;
271 cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
272 } catch (final PathParsingException e) {
273 throw new CpsPathException(e.getMessage());
275 List<FragmentEntity> fragmentEntities =
276 fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
277 if (cpsPathQuery.hasAncestorAxis()) {
278 final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
279 fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList()
280 : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
282 return createDataNodesFromFragmentEntities(fetchDescendantsOption, anchorEntity,
286 private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption,
287 final AnchorEntity anchorEntity,
288 final List<FragmentEntity> fragmentEntities) {
289 final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
290 for (final FragmentEntity proxiedFragmentEntity : fragmentEntities) {
291 final DataNode dataNode;
292 if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
293 dataNode = toDataNode(proxiedFragmentEntity, fetchDescendantsOption);
295 final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath());
296 final FragmentEntity unproxiedFragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity,
298 dataNode = toDataNode(unproxiedFragmentEntity, fetchDescendantsOption);
300 dataNodes.add(dataNode);
302 return Collections.unmodifiableList(dataNodes);
305 private static String getNormalizedXpath(final String xpathSource) {
306 final String normalizedXpath;
308 normalizedXpath = CpsPathUtil.getNormalizedXpath(xpathSource);
309 } catch (final PathParsingException e) {
310 throw new CpsPathException(e.getMessage());
312 return normalizedXpath;
316 public String startSession() {
317 return sessionManager.startSession();
321 public void closeSession(final String sessionId) {
322 sessionManager.closeSession(sessionId, SessionManager.WITH_COMMIT);
326 public void lockAnchor(final String sessionId, final String dataspaceName,
327 final String anchorName, final Long timeoutInMilliseconds) {
328 sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds);
331 private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
332 final CpsPathQuery cpsPathQuery) {
333 final Set<String> ancestorXpath = new HashSet<>();
334 final Pattern pattern =
335 Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
336 + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*");
337 for (final FragmentEntity fragmentEntity : fragmentEntities) {
338 final Matcher matcher = pattern.matcher(fragmentEntity.getXpath());
339 if (matcher.matches()) {
340 ancestorXpath.add(matcher.group(1));
343 return ancestorXpath;
346 private DataNode toDataNode(final FragmentEntity fragmentEntity,
347 final FetchDescendantsOption fetchDescendantsOption) {
348 final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
349 Map<String, Object> leaves = new HashMap<>();
350 if (fragmentEntity.getAttributes() != null) {
351 leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
353 return new DataNodeBuilder()
354 .withXpath(fragmentEntity.getXpath())
356 .withChildDataNodes(childDataNodes).build();
359 private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
360 final FetchDescendantsOption fetchDescendantsOption) {
361 if (fetchDescendantsOption.hasNext()) {
362 return fragmentEntity.getChildFragments().stream()
363 .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next()))
364 .collect(Collectors.toList());
366 return Collections.emptyList();
370 public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
371 final Map<String, Object> leaves) {
372 final FragmentEntity fragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, xpath);
373 fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(leaves));
374 fragmentRepository.save(fragmentEntity);
378 public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
379 final DataNode dataNode) {
380 final FragmentEntity fragmentEntity =
381 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath());
382 updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
384 fragmentRepository.save(fragmentEntity);
385 } catch (final StaleStateException staleStateException) {
386 throw new ConcurrencyException("Concurrent Transactions",
387 String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
388 dataspaceName, anchorName, dataNode.getXpath()));
393 public void updateDataNodesAndDescendants(final String dataspaceName,
394 final String anchorName,
395 final List<DataNode> dataNodes) {
397 final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
398 .collect(Collectors.toMap(
399 dataNode -> dataNode,
401 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath())));
402 dataNodeFragmentEntityMap.forEach(
403 (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode));
405 fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
406 } catch (final StaleStateException staleStateException) {
407 retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values());
411 private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
412 final Collection<FragmentEntity> fragmentEntities) {
413 final Collection<String> failedXpaths = new HashSet<>();
415 fragmentEntities.forEach(dataNodeFragment -> {
417 fragmentRepository.save(dataNodeFragment);
418 } catch (final StaleStateException e) {
419 failedXpaths.add(dataNodeFragment.getXpath());
423 if (!failedXpaths.isEmpty()) {
424 final String failedXpathsConcatenated = String.join(",", failedXpaths);
425 throw new ConcurrencyException("Concurrent Transactions", String.format(
426 "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.",
427 failedXpathsConcatenated, dataspaceName, anchorName));
431 private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity,
432 final DataNode newDataNode) {
434 existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
436 final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream()
437 .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
439 final Collection<FragmentEntity> updatedChildFragments = new HashSet<>();
441 for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) {
442 final FragmentEntity childFragment;
443 if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) {
444 childFragment = convertToFragmentWithAllDescendants(
445 existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild);
447 childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath());
448 updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild);
450 updatedChildFragments.add(childFragment);
453 existingFragmentEntity.getChildFragments().clear();
454 existingFragmentEntity.getChildFragments().addAll(updatedChildFragments);
459 public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
460 final Collection<DataNode> newListElements) {
461 final FragmentEntity parentEntity =
462 getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
463 final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
464 final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
465 extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
466 deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
467 final Set<FragmentEntity> updatedChildFragmentEntities = new HashSet<>();
468 for (final DataNode newListElement : newListElements) {
469 final FragmentEntity existingListElementEntity =
470 existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath());
471 final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement,
472 existingListElementEntity);
474 updatedChildFragmentEntities.add(entityToBeAdded);
476 parentEntity.getChildFragments().addAll(updatedChildFragmentEntities);
477 fragmentRepository.save(parentEntity);
482 public void deleteDataNodes(final String dataspaceName, final String anchorName) {
483 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
484 anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName)
486 anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity)));
491 public void deleteListDataNode(final String dataspaceName, final String anchorName,
492 final String targetXpath) {
493 deleteDataNode(dataspaceName, anchorName, targetXpath, true);
498 public void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) {
499 deleteDataNode(dataspaceName, anchorName, targetXpath, false);
502 private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath,
503 final boolean onlySupportListNodeDeletion) {
504 final String parentNodeXpath;
505 FragmentEntity parentFragmentEntity = null;
506 boolean targetDeleted;
507 if (isRootXpath(targetXpath)) {
508 deleteDataNodes(dataspaceName, anchorName);
509 targetDeleted = true;
511 if (isRootContainerNodeXpath(targetXpath)) {
512 parentNodeXpath = targetXpath;
514 parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/'));
516 parentFragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
517 final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/'));
518 final boolean isListElement = REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE
519 .matcher(lastXpathElement).find();
521 targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
523 targetDeleted = deleteAllListElements(parentFragmentEntity, targetXpath);
524 final boolean tryToDeleteDataNode = !targetDeleted && !onlySupportListNodeDeletion;
525 if (tryToDeleteDataNode) {
526 targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
530 if (!targetDeleted) {
531 final String additionalInformation = onlySupportListNodeDeletion
532 ? "The target is probably not a List." : "";
533 throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(),
534 parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation);
538 private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) {
539 final String normalizedTargetXpath = CpsPathUtil.getNormalizedXpath(targetXpath);
540 if (parentFragmentEntity.getXpath().equals(normalizedTargetXpath)) {
541 fragmentRepository.delete(parentFragmentEntity);
544 if (parentFragmentEntity.getChildFragments()
545 .removeIf(fragment -> fragment.getXpath().equals(normalizedTargetXpath))) {
546 fragmentRepository.save(parentFragmentEntity);
552 private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) {
553 final String normalizedListXpath = CpsPathUtil.getNormalizedXpath(listXpath);
554 final String deleteTargetXpathPrefix = normalizedListXpath + "[";
555 if (parentFragmentEntity.getChildFragments()
556 .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) {
557 fragmentRepository.save(parentFragmentEntity);
563 private static void deleteListElements(
564 final Collection<FragmentEntity> fragmentEntities,
565 final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath) {
566 fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values());
569 private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
570 if (newListElements.isEmpty()) {
571 throw new CpsAdminException("Invalid list replacement",
572 "Cannot replace list elements with empty collection");
574 final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
575 return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1);
578 private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
579 final DataNode newListElement,
580 final FragmentEntity existingListElementEntity) {
581 if (existingListElementEntity == null) {
582 return convertToFragmentWithAllDescendants(
583 parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement);
585 if (newListElement.getChildDataNodes().isEmpty()) {
586 copyAttributesFromNewListElement(existingListElementEntity, newListElement);
587 existingListElementEntity.getChildFragments().clear();
589 updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement);
591 return existingListElementEntity;
594 private static boolean isNewDataNode(final DataNode replacementDataNode,
595 final Map<String, FragmentEntity> existingListElementsByXpath) {
596 return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
599 private static boolean isRootContainerNodeXpath(final String xpath) {
600 return 0 == xpath.lastIndexOf('/');
603 private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
604 final DataNode newListElement) {
605 final FragmentEntity replacementFragmentEntity =
606 FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString(
607 newListElement.getLeaves())).build();
608 existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
611 private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
612 final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
613 return childEntities.stream()
614 .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix))
615 .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
618 private static boolean isRootXpath(final String xpath) {
619 return "/".equals(xpath) || "".equals(xpath);