2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2023 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2020-2022 Bell Canada.
6 * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.spi.impl;
26 import com.google.common.collect.ImmutableSet;
27 import com.google.common.collect.ImmutableSet.Builder;
28 import java.io.Serializable;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
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.entities.AnchorEntity;
50 import org.onap.cps.spi.entities.DataspaceEntity;
51 import org.onap.cps.spi.entities.FragmentEntity;
52 import org.onap.cps.spi.entities.FragmentEntityArranger;
53 import org.onap.cps.spi.entities.FragmentExtract;
54 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
55 import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
56 import org.onap.cps.spi.exceptions.ConcurrencyException;
57 import org.onap.cps.spi.exceptions.CpsAdminException;
58 import org.onap.cps.spi.exceptions.CpsPathException;
59 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
60 import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch;
61 import org.onap.cps.spi.model.DataNode;
62 import org.onap.cps.spi.model.DataNodeBuilder;
63 import org.onap.cps.spi.repository.AnchorRepository;
64 import org.onap.cps.spi.repository.DataspaceRepository;
65 import org.onap.cps.spi.repository.FragmentQueryBuilder;
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.springframework.dao.DataIntegrityViolationException;
70 import org.springframework.stereotype.Service;
74 @RequiredArgsConstructor
75 public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
77 private final DataspaceRepository dataspaceRepository;
78 private final AnchorRepository anchorRepository;
79 private final FragmentRepository fragmentRepository;
80 private final JsonObjectMapper jsonObjectMapper;
81 private final SessionManager sessionManager;
83 private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?])?)";
86 public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
87 final DataNode newChildDataNode) {
88 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
89 addNewChildDataNode(anchorEntity, parentNodeXpath, newChildDataNode);
93 public void addChildDataNodes(final String dataspaceName, final String anchorName,
94 final String parentNodeXpath, final Collection<DataNode> dataNodes) {
95 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
96 addChildrenDataNodes(anchorEntity, parentNodeXpath, dataNodes);
100 public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
101 final Collection<DataNode> newListElements) {
102 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
103 addChildrenDataNodes(anchorEntity, parentNodeXpath, newListElements);
107 public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
108 final Collection<Collection<DataNode>> newLists) {
109 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
110 final Collection<String> failedXpaths = new HashSet<>();
111 for (final Collection<DataNode> newList : newLists) {
113 addChildrenDataNodes(anchorEntity, parentNodeXpath, newList);
114 } catch (final AlreadyDefinedExceptionBatch e) {
115 failedXpaths.addAll(e.getAlreadyDefinedXpaths());
118 if (!failedXpaths.isEmpty()) {
119 throw new AlreadyDefinedExceptionBatch(failedXpaths);
123 private void addNewChildDataNode(final AnchorEntity anchorEntity, final String parentNodeXpath,
124 final DataNode newChild) {
125 final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath);
126 final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(anchorEntity, newChild);
127 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
129 fragmentRepository.save(newChildAsFragmentEntity);
130 } catch (final DataIntegrityViolationException e) {
131 throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorEntity.getName(), e);
135 private void addChildrenDataNodes(final AnchorEntity anchorEntity, final String parentNodeXpath,
136 final Collection<DataNode> newChildren) {
137 final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath);
138 final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
140 for (final DataNode newChildAsDataNode : newChildren) {
141 final FragmentEntity newChildAsFragmentEntity =
142 convertToFragmentWithAllDescendants(anchorEntity, newChildAsDataNode);
143 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
144 fragmentEntities.add(newChildAsFragmentEntity);
146 fragmentRepository.saveAll(fragmentEntities);
147 } catch (final DataIntegrityViolationException e) {
148 log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
149 e, fragmentEntities.size());
150 retrySavingEachChildIndividually(anchorEntity, parentNodeXpath, newChildren);
154 private void retrySavingEachChildIndividually(final AnchorEntity anchorEntity, final String parentNodeXpath,
155 final Collection<DataNode> newChildren) {
156 final Collection<String> failedXpaths = new HashSet<>();
157 for (final DataNode newChild : newChildren) {
159 addNewChildDataNode(anchorEntity, parentNodeXpath, newChild);
160 } catch (final AlreadyDefinedException e) {
161 failedXpaths.add(newChild.getXpath());
164 if (!failedXpaths.isEmpty()) {
165 throw new AlreadyDefinedExceptionBatch(failedXpaths);
170 public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
171 storeDataNodes(dataspaceName, anchorName, Collections.singletonList(dataNode));
175 public void storeDataNodes(final String dataspaceName, final String anchorName,
176 final Collection<DataNode> dataNodes) {
177 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
178 final List<FragmentEntity> fragmentEntities = new ArrayList<>(dataNodes.size());
180 for (final DataNode dataNode: dataNodes) {
181 final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(anchorEntity, dataNode);
182 fragmentEntities.add(fragmentEntity);
184 fragmentRepository.saveAll(fragmentEntities);
185 } catch (final DataIntegrityViolationException exception) {
186 log.warn("Exception occurred : {} , While saving : {} data nodes, Retrying saving data nodes individually",
187 exception, dataNodes.size());
188 storeDataNodesIndividually(anchorEntity, dataNodes);
192 private void storeDataNodesIndividually(final AnchorEntity anchorEntity, final Collection<DataNode> dataNodes) {
193 final Collection<String> failedXpaths = new HashSet<>();
194 for (final DataNode dataNode: dataNodes) {
196 final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(anchorEntity, dataNode);
197 fragmentRepository.save(fragmentEntity);
198 } catch (final DataIntegrityViolationException e) {
199 failedXpaths.add(dataNode.getXpath());
202 if (!failedXpaths.isEmpty()) {
203 throw new AlreadyDefinedExceptionBatch(failedXpaths);
208 * Convert DataNode object into Fragment and places the result in the fragments placeholder. Performs same action
209 * for all DataNode children recursively.
211 * @param anchorEntity anchorEntity
212 * @param dataNodeToBeConverted dataNode
213 * @return a Fragment built from current DataNode
215 private FragmentEntity convertToFragmentWithAllDescendants(final AnchorEntity anchorEntity,
216 final DataNode dataNodeToBeConverted) {
217 final FragmentEntity parentFragment = toFragmentEntity(anchorEntity, dataNodeToBeConverted);
218 final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
219 for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
220 final FragmentEntity childFragment = convertToFragmentWithAllDescendants(anchorEntity, childDataNode);
221 childFragmentsImmutableSetBuilder.add(childFragment);
223 parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
224 return parentFragment;
227 private FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, final DataNode dataNode) {
228 return FragmentEntity.builder()
229 .dataspace(anchorEntity.getDataspace())
230 .anchor(anchorEntity)
231 .xpath(dataNode.getXpath())
232 .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves()))
237 public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
239 final FetchDescendantsOption fetchDescendantsOption) {
240 final String targetXpath = getNormalizedXpath(xpath);
241 final Collection<DataNode> dataNodes = getDataNodesForMultipleXpaths(dataspaceName, anchorName,
242 Collections.singletonList(targetXpath), fetchDescendantsOption);
243 if (dataNodes.isEmpty()) {
244 throw new DataNodeNotFoundException(dataspaceName, anchorName, xpath);
250 public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
251 final Collection<String> xpaths,
252 final FetchDescendantsOption fetchDescendantsOption) {
253 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
254 final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(anchorEntity, xpaths);
255 return toDataNodes(fragmentEntities, fetchDescendantsOption);
258 private Collection<FragmentEntity> getFragmentEntities(final AnchorEntity anchorEntity,
259 final Collection<String> xpaths) {
260 final Collection<String> nonRootXpaths = new HashSet<>(xpaths);
261 final boolean haveRootXpath = nonRootXpaths.removeIf(CpsDataPersistenceServiceImpl::isRootXpath);
263 final Collection<String> normalizedXpaths = new HashSet<>(nonRootXpaths.size());
264 for (final String xpath : nonRootXpaths) {
266 normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath));
267 } catch (final PathParsingException e) {
268 log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage());
271 final Collection<FragmentEntity> fragmentEntities =
272 new HashSet<>(fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorEntity.getId(), normalizedXpaths));
275 final List<FragmentExtract> fragmentExtracts = fragmentRepository.findAllExtractsByAnchor(anchorEntity);
276 fragmentEntities.addAll(FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts));
279 return fragmentEntities;
282 private FragmentEntity getFragmentEntity(final AnchorEntity anchorEntity, final String xpath) {
283 final FragmentEntity fragmentEntity;
284 if (isRootXpath(xpath)) {
285 final List<FragmentExtract> fragmentExtracts = fragmentRepository.findAllExtractsByAnchor(anchorEntity);
286 fragmentEntity = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts)
287 .stream().findFirst().orElse(null);
289 fragmentEntity = fragmentRepository.getByAnchorAndXpath(anchorEntity, getNormalizedXpath(xpath));
291 if (fragmentEntity == null) {
292 throw new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath);
294 return fragmentEntity;
297 private Collection<FragmentEntity> buildFragmentEntitiesFromFragmentExtracts(final AnchorEntity anchorEntity,
298 final String normalizedXpath) {
299 final List<FragmentExtract> fragmentExtracts =
300 fragmentRepository.findByAnchorAndParentXpath(anchorEntity, normalizedXpath);
301 return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
305 public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
306 final FetchDescendantsOption fetchDescendantsOption) {
307 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
308 final CpsPathQuery cpsPathQuery;
310 cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
311 } catch (final PathParsingException e) {
312 throw new CpsPathException(e.getMessage());
315 Collection<FragmentEntity> fragmentEntities;
316 if (canUseRegexQuickFind(fetchDescendantsOption, cpsPathQuery)) {
317 return getDataNodesUsingRegexQuickFind(fetchDescendantsOption, anchorEntity, cpsPathQuery);
319 fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
320 if (cpsPathQuery.hasAncestorAxis()) {
321 fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
323 return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities);
326 private static boolean canUseRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption,
327 final CpsPathQuery cpsPathQuery) {
328 return fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
329 && !cpsPathQuery.hasLeafConditions()
330 && !cpsPathQuery.hasTextFunctionCondition();
333 private List<DataNode> getDataNodesUsingRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption,
334 final AnchorEntity anchorEntity,
335 final CpsPathQuery cpsPathQuery) {
336 Collection<FragmentEntity> fragmentEntities;
337 final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true);
338 final List<FragmentExtract> fragmentExtracts =
339 fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex);
340 fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
341 if (cpsPathQuery.hasAncestorAxis()) {
342 fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
344 return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
347 private Collection<FragmentEntity> getAncestorFragmentEntities(final int anchorId,
348 final CpsPathQuery cpsPathQuery,
349 final Collection<FragmentEntity> fragmentEntities) {
350 final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
351 return ancestorXpaths.isEmpty() ? Collections.emptyList()
352 : fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorId, ancestorXpaths);
355 private List<DataNode> createDataNodesFromProxiedFragmentEntities(
356 final FetchDescendantsOption fetchDescendantsOption,
357 final AnchorEntity anchorEntity,
358 final Collection<FragmentEntity> proxiedFragmentEntities) {
359 final List<DataNode> dataNodes = new ArrayList<>(proxiedFragmentEntities.size());
360 for (final FragmentEntity proxiedFragmentEntity : proxiedFragmentEntities) {
361 if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
362 dataNodes.add(toDataNode(proxiedFragmentEntity, fetchDescendantsOption));
364 final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath());
365 final Collection<FragmentEntity> unproxiedFragmentEntities =
366 buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath);
367 for (final FragmentEntity unproxiedFragmentEntity : unproxiedFragmentEntities) {
368 dataNodes.add(toDataNode(unproxiedFragmentEntity, fetchDescendantsOption));
372 return Collections.unmodifiableList(dataNodes);
375 private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption,
376 final Collection<FragmentEntity> fragmentEntities) {
377 final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
378 for (final FragmentEntity fragmentEntity : fragmentEntities) {
379 dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption));
381 return Collections.unmodifiableList(dataNodes);
384 private static String getNormalizedXpath(final String xpathSource) {
385 if (isRootXpath(xpathSource)) {
389 return CpsPathUtil.getNormalizedXpath(xpathSource);
390 } catch (final PathParsingException e) {
391 throw new CpsPathException(e.getMessage());
396 public String startSession() {
397 return sessionManager.startSession();
401 public void closeSession(final String sessionId) {
402 sessionManager.closeSession(sessionId, SessionManager.WITH_COMMIT);
406 public void lockAnchor(final String sessionId, final String dataspaceName,
407 final String anchorName, final Long timeoutInMilliseconds) {
408 sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds);
411 private static Set<String> processAncestorXpath(final Collection<FragmentEntity> fragmentEntities,
412 final CpsPathQuery cpsPathQuery) {
413 final Set<String> ancestorXpath = new HashSet<>();
414 final Pattern pattern =
415 Pattern.compile("([\\s\\S]*/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
416 + REG_EX_FOR_OPTIONAL_LIST_INDEX + "/[\\s\\S]*");
417 for (final FragmentEntity fragmentEntity : fragmentEntities) {
418 final Matcher matcher = pattern.matcher(fragmentEntity.getXpath());
419 if (matcher.matches()) {
420 ancestorXpath.add(matcher.group(1));
423 return ancestorXpath;
426 private DataNode toDataNode(final FragmentEntity fragmentEntity,
427 final FetchDescendantsOption fetchDescendantsOption) {
428 final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
429 Map<String, Serializable> leaves = new HashMap<>();
430 if (fragmentEntity.getAttributes() != null) {
431 leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
433 return new DataNodeBuilder()
434 .withXpath(fragmentEntity.getXpath())
436 .withChildDataNodes(childDataNodes).build();
439 private Collection<DataNode> toDataNodes(final Collection<FragmentEntity> fragmentEntities,
440 final FetchDescendantsOption fetchDescendantsOption) {
441 final Collection<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
442 for (final FragmentEntity fragmentEntity : fragmentEntities) {
443 dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption));
448 private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
449 final FetchDescendantsOption fetchDescendantsOption) {
450 if (fetchDescendantsOption.hasNext()) {
451 return fragmentEntity.getChildFragments().stream()
452 .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next()))
453 .collect(Collectors.toList());
455 return Collections.emptyList();
459 public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
460 final Map<String, Serializable> updateLeaves) {
461 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
462 final FragmentEntity fragmentEntity = getFragmentEntity(anchorEntity, xpath);
463 final String currentLeavesAsString = fragmentEntity.getAttributes();
464 final String mergedLeaves = mergeLeaves(updateLeaves, currentLeavesAsString);
465 fragmentEntity.setAttributes(mergedLeaves);
466 fragmentRepository.save(fragmentEntity);
470 public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
471 final DataNode dataNode) {
472 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
473 final FragmentEntity fragmentEntity = getFragmentEntity(anchorEntity, dataNode.getXpath());
474 updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
476 fragmentRepository.save(fragmentEntity);
477 } catch (final StaleStateException staleStateException) {
478 throw new ConcurrencyException("Concurrent Transactions",
479 String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
480 dataspaceName, anchorName, dataNode.getXpath()));
485 public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
486 final List<DataNode> updatedDataNodes) {
487 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
489 final Map<String, DataNode> xpathToUpdatedDataNode = updatedDataNodes.stream()
490 .collect(Collectors.toMap(DataNode::getXpath, dataNode -> dataNode));
492 final Collection<String> xpaths = xpathToUpdatedDataNode.keySet();
493 final Collection<FragmentEntity> existingFragmentEntities = getFragmentEntities(anchorEntity, xpaths);
495 for (final FragmentEntity existingFragmentEntity : existingFragmentEntities) {
496 final DataNode updatedDataNode = xpathToUpdatedDataNode.get(existingFragmentEntity.getXpath());
497 updateFragmentEntityAndDescendantsWithDataNode(existingFragmentEntity, updatedDataNode);
501 fragmentRepository.saveAll(existingFragmentEntities);
502 } catch (final StaleStateException staleStateException) {
503 retryUpdateDataNodesIndividually(anchorEntity, existingFragmentEntities);
507 private void retryUpdateDataNodesIndividually(final AnchorEntity anchorEntity,
508 final Collection<FragmentEntity> fragmentEntities) {
509 final Collection<String> failedXpaths = new HashSet<>();
510 for (final FragmentEntity dataNodeFragment : fragmentEntities) {
512 fragmentRepository.save(dataNodeFragment);
513 } catch (final StaleStateException e) {
514 failedXpaths.add(dataNodeFragment.getXpath());
517 if (!failedXpaths.isEmpty()) {
518 final String failedXpathsConcatenated = String.join(",", failedXpaths);
519 throw new ConcurrencyException("Concurrent Transactions", String.format(
520 "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.",
521 failedXpathsConcatenated, anchorEntity.getDataspace().getName(), anchorEntity.getName()));
525 private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity,
526 final DataNode newDataNode) {
527 existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
529 final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream()
530 .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
532 final Collection<FragmentEntity> updatedChildFragments = new HashSet<>();
533 for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) {
534 final FragmentEntity childFragment;
535 if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) {
536 childFragment = convertToFragmentWithAllDescendants(existingFragmentEntity.getAnchor(),
539 childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath());
540 updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild);
542 updatedChildFragments.add(childFragment);
545 existingFragmentEntity.getChildFragments().clear();
546 existingFragmentEntity.getChildFragments().addAll(updatedChildFragments);
551 public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
552 final Collection<DataNode> newListElements) {
553 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
554 final FragmentEntity parentEntity = getFragmentEntity(anchorEntity, parentNodeXpath);
555 final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
556 final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
557 extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
558 parentEntity.getChildFragments().removeAll(existingListElementFragmentEntitiesByXPath.values());
559 final Set<FragmentEntity> updatedChildFragmentEntities = new HashSet<>();
560 for (final DataNode newListElement : newListElements) {
561 final FragmentEntity existingListElementEntity =
562 existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath());
563 final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement,
564 existingListElementEntity);
565 updatedChildFragmentEntities.add(entityToBeAdded);
567 parentEntity.getChildFragments().addAll(updatedChildFragmentEntities);
568 fragmentRepository.save(parentEntity);
573 public void deleteDataNodes(final String dataspaceName, final String anchorName) {
574 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
575 anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName)
576 .ifPresent(anchorEntity -> fragmentRepository.deleteByAnchorIn(Collections.singletonList(anchorEntity)));
581 public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames) {
582 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
583 final Collection<AnchorEntity> anchorEntities =
584 anchorRepository.findAllByDataspaceAndNameIn(dataspaceEntity, anchorNames);
585 fragmentRepository.deleteByAnchorIn(anchorEntities);
590 public void deleteDataNodes(final String dataspaceName, final String anchorName,
591 final Collection<String> xpathsToDelete) {
592 deleteDataNodes(dataspaceName, anchorName, xpathsToDelete, false);
595 private void deleteDataNodes(final String dataspaceName, final String anchorName,
596 final Collection<String> xpathsToDelete, final boolean onlySupportListDeletion) {
597 final boolean haveRootXpath = xpathsToDelete.stream().anyMatch(CpsDataPersistenceServiceImpl::isRootXpath);
599 deleteDataNodes(dataspaceName, anchorName);
603 final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
605 final Collection<String> deleteChecklist = new HashSet<>(xpathsToDelete.size());
606 for (final String xpath : xpathsToDelete) {
608 deleteChecklist.add(CpsPathUtil.getNormalizedXpath(xpath));
609 } catch (final PathParsingException e) {
610 log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage());
614 final Collection<String> xpathsToExistingContainers =
615 fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist);
616 if (onlySupportListDeletion) {
617 final Collection<String> xpathsToExistingListElements = xpathsToExistingContainers.stream()
618 .filter(CpsPathUtil::isPathToListElement).collect(Collectors.toList());
619 deleteChecklist.removeAll(xpathsToExistingListElements);
621 deleteChecklist.removeAll(xpathsToExistingContainers);
624 final Collection<String> xpathsToExistingLists = deleteChecklist.stream()
625 .filter(xpath -> fragmentRepository.existsByAnchorAndXpathStartsWith(anchorEntity, xpath + "["))
626 .collect(Collectors.toList());
627 deleteChecklist.removeAll(xpathsToExistingLists);
629 if (!deleteChecklist.isEmpty()) {
630 throw new DataNodeNotFoundExceptionBatch(dataspaceName, anchorName, deleteChecklist);
633 fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingContainers);
634 fragmentRepository.deleteListsByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingLists);
639 public void deleteListDataNode(final String dataspaceName, final String anchorName,
640 final String targetXpath) {
641 deleteDataNode(dataspaceName, anchorName, targetXpath, true);
646 public void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) {
647 deleteDataNode(dataspaceName, anchorName, targetXpath, false);
650 private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath,
651 final boolean onlySupportListNodeDeletion) {
652 final String normalizedXpath = getNormalizedXpath(targetXpath);
654 deleteDataNodes(dataspaceName, anchorName, Collections.singletonList(normalizedXpath),
655 onlySupportListNodeDeletion);
656 } catch (final DataNodeNotFoundExceptionBatch dataNodeNotFoundExceptionBatch) {
657 throw new DataNodeNotFoundException(dataspaceName, anchorName, targetXpath);
661 private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
662 if (newListElements.isEmpty()) {
663 throw new CpsAdminException("Invalid list replacement",
664 "Cannot replace list elements with empty collection");
666 final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
667 return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1);
670 private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
671 final DataNode newListElement,
672 final FragmentEntity existingListElementEntity) {
673 if (existingListElementEntity == null) {
674 return convertToFragmentWithAllDescendants(parentEntity.getAnchor(), newListElement);
676 if (newListElement.getChildDataNodes().isEmpty()) {
677 copyAttributesFromNewListElement(existingListElementEntity, newListElement);
678 existingListElementEntity.getChildFragments().clear();
680 updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement);
682 return existingListElementEntity;
685 private static boolean isNewDataNode(final DataNode replacementDataNode,
686 final Map<String, FragmentEntity> existingListElementsByXpath) {
687 return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
690 private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
691 final DataNode newListElement) {
692 final FragmentEntity replacementFragmentEntity =
693 FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString(
694 newListElement.getLeaves())).build();
695 existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
698 private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
699 final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
700 return childEntities.stream()
701 .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix))
702 .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
705 private static boolean isRootXpath(final String xpath) {
706 return "/".equals(xpath) || "".equals(xpath);
709 private String mergeLeaves(final Map<String, Serializable> updateLeaves, final String currentLeavesAsString) {
710 final Map<String, Serializable> currentLeavesAsMap = currentLeavesAsString.isEmpty()
711 ? new HashMap<>() : jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class);
712 currentLeavesAsMap.putAll(updateLeaves);
713 if (currentLeavesAsMap.isEmpty()) {
716 return jsonObjectMapper.asJsonString(currentLeavesAsMap);
719 private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) {
720 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
721 return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);