/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021 Nordix Foundation
+ * Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2020-2021 Bell Canada.
+ * Modifications Copyright (C) 2020-2022 Bell Canada.
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
package org.onap.cps.spi.impl;
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
+import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.StaleStateException;
import org.onap.cps.cpspath.parser.CpsPathQuery;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
+import org.onap.cps.cpspath.parser.PathParsingException;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.entities.AnchorEntity;
import org.onap.cps.spi.entities.DataspaceEntity;
import org.onap.cps.spi.entities.FragmentEntity;
+import org.onap.cps.spi.entities.FragmentEntityArranger;
+import org.onap.cps.spi.entities.FragmentExtract;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
import org.onap.cps.spi.exceptions.ConcurrencyException;
+import org.onap.cps.spi.exceptions.CpsAdminException;
import org.onap.cps.spi.exceptions.CpsPathException;
import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
-import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.spi.model.DataNode;
import org.onap.cps.spi.model.DataNodeBuilder;
import org.onap.cps.spi.repository.AnchorRepository;
import org.onap.cps.spi.repository.DataspaceRepository;
+import org.onap.cps.spi.repository.FragmentQueryBuilder;
import org.onap.cps.spi.repository.FragmentRepository;
+import org.onap.cps.spi.utils.SessionManager;
+import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
@Service
@Slf4j
+@RequiredArgsConstructor
public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
- private DataspaceRepository dataspaceRepository;
-
- private AnchorRepository anchorRepository;
+ private final DataspaceRepository dataspaceRepository;
+ private final AnchorRepository anchorRepository;
+ private final FragmentRepository fragmentRepository;
+ private final JsonObjectMapper jsonObjectMapper;
+ private final SessionManager sessionManager;
- private FragmentRepository fragmentRepository;
+ private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
- private final ObjectMapper objectMapper;
+ @Override
+ public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final DataNode newChildDataNode) {
+ addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
+ }
- /**
- * Constructor.
- *
- * @param dataspaceRepository dataspaceRepository
- * @param anchorRepository anchorRepository
- * @param fragmentRepository fragmentRepository
- */
- public CpsDataPersistenceServiceImpl(final DataspaceRepository dataspaceRepository,
- final AnchorRepository anchorRepository, final FragmentRepository fragmentRepository) {
- this.dataspaceRepository = dataspaceRepository;
- this.anchorRepository = anchorRepository;
- this.fragmentRepository = fragmentRepository;
- objectMapper = new ObjectMapper();
+ @Override
+ public void addChildDataNodes(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath, final Collection<DataNode> dataNodes) {
+ addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
}
- private static final Gson GSON = new GsonBuilder().create();
- private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
- private static final String REG_EX_FOR_LIST_NODE_KEY = "\\[(\\@([^/]*?)){0,99}( and)*\\]$";
+ @Override
+ public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> newListElements) {
+ addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
+ }
@Override
- public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath,
- final DataNode dataNode) {
- final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentXpath);
- final FragmentEntity fragmentEntity =
- toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode);
- parentFragment.getChildFragments().add(fragmentEntity);
+ public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<Collection<DataNode>> newLists) {
+ final Collection<String> failedXpaths = new HashSet<>();
+ newLists.forEach(newList -> {
+ try {
+ addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
+ } catch (final AlreadyDefinedExceptionBatch e) {
+ failedXpaths.addAll(e.getAlreadyDefinedXpaths());
+ }
+ });
+
+ if (!failedXpaths.isEmpty()) {
+ throw new AlreadyDefinedExceptionBatch(failedXpaths);
+ }
+
+ }
+
+ private void addNewChildDataNode(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath, final DataNode newChild) {
+ final FragmentEntity parentFragmentEntity =
+ getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
+ final FragmentEntity newChildAsFragmentEntity =
+ convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+ parentFragmentEntity.getAnchor(), newChild);
+ newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
try {
- fragmentRepository.save(parentFragment);
- } catch (final DataIntegrityViolationException exception) {
- throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception);
+ fragmentRepository.save(newChildAsFragmentEntity);
+ } catch (final DataIntegrityViolationException e) {
+ throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
}
+
}
- @Override
- public void addListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final Collection<DataNode> dataNodes) {
- final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
- final List<FragmentEntity> newFragmentEntities =
- dataNodes.stream().map(
- dataNode -> toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode)
- ).collect(Collectors.toUnmodifiableList());
- parentFragment.getChildFragments().addAll(newFragmentEntities);
+ private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> newChildren) {
+ final FragmentEntity parentFragmentEntity =
+ getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
+ final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
try {
- fragmentRepository.save(parentFragment);
- dataNodes.forEach(
- dataNode -> getChildFragments(dataspaceName, anchorName, dataNode)
- );
- } catch (final DataIntegrityViolationException exception) {
- final List<String> conflictXpaths = dataNodes.stream()
- .map(DataNode::getXpath)
- .collect(Collectors.toList());
- throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception);
+ newChildren.forEach(newChildAsDataNode -> {
+ final FragmentEntity newChildAsFragmentEntity =
+ convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+ parentFragmentEntity.getAnchor(), newChildAsDataNode);
+ newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
+ fragmentEntities.add(newChildAsFragmentEntity);
+ });
+ fragmentRepository.saveAll(fragmentEntities);
+ } catch (final DataIntegrityViolationException e) {
+ log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
+ e, fragmentEntities.size());
+ retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
+ }
+ }
+
+ private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath,
+ final Collection<DataNode> newChildren) {
+ final Collection<String> failedXpaths = new HashSet<>();
+ for (final DataNode newChild : newChildren) {
+ try {
+ addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild);
+ } catch (final AlreadyDefinedException e) {
+ failedXpaths.add(newChild.getXpath());
+ }
+ }
+ if (!failedXpaths.isEmpty()) {
+ throw new AlreadyDefinedExceptionBatch(failedXpaths);
}
}
@Override
public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
+ storeDataNodes(dataspaceName, anchorName, Collections.singletonList(dataNode));
+ }
+
+ @Override
+ public void storeDataNodes(final String dataspaceName, final String anchorName,
+ final Collection<DataNode> dataNodes) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
- final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
- dataNode);
+ final List<FragmentEntity> fragmentEntities = new ArrayList<>(dataNodes.size());
try {
- fragmentRepository.save(fragmentEntity);
+ for (final DataNode dataNode: dataNodes) {
+ final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
+ dataNode);
+ fragmentEntities.add(fragmentEntity);
+ }
+ fragmentRepository.saveAll(fragmentEntities);
} catch (final DataIntegrityViolationException exception) {
- throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception);
+ log.warn("Exception occurred : {} , While saving : {} data nodes, Retrying saving data nodes individually",
+ exception, dataNodes.size());
+ storeDataNodesIndividually(dataspaceName, anchorName, dataNodes);
+ }
+ }
+
+ private void storeDataNodesIndividually(final String dataspaceName, final String anchorName,
+ final Collection<DataNode> dataNodes) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+ final Collection<String> failedXpaths = new HashSet<>();
+ for (final DataNode dataNode: dataNodes) {
+ try {
+ final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
+ dataNode);
+ fragmentRepository.save(fragmentEntity);
+ } catch (final DataIntegrityViolationException e) {
+ failedXpaths.add(dataNode.getXpath());
+ }
+ }
+ if (!failedXpaths.isEmpty()) {
+ throw new AlreadyDefinedExceptionBatch(failedXpaths);
}
}
* @param dataNodeToBeConverted dataNode
* @return a Fragment built from current DataNode
*/
- private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
- final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) {
+ private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
+ final AnchorEntity anchorEntity,
+ final DataNode dataNodeToBeConverted) {
final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
final FragmentEntity childFragment =
- convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(),
- childDataNode);
+ convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(),
+ childDataNode);
childFragmentsImmutableSetBuilder.add(childFragment);
}
parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
return parentFragment;
}
- private void getChildFragments(final String dataspaceName, final String anchorName, final DataNode dataNode) {
- for (final DataNode childDataNode: dataNode.getChildDataNodes()) {
- final FragmentEntity getChildsParentFragmentByXPath =
- getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath());
- final FragmentEntity childFragmentEntity = toFragmentEntity(getChildsParentFragmentByXPath.getDataspace(),
- getChildsParentFragmentByXPath.getAnchor(), childDataNode);
- getChildsParentFragmentByXPath.getChildFragments().add(childFragmentEntity);
- fragmentRepository.save(getChildsParentFragmentByXPath);
- }
+ private FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
+ final AnchorEntity anchorEntity, final DataNode dataNode) {
+ return FragmentEntity.builder()
+ .dataspace(dataspaceEntity)
+ .anchor(anchorEntity)
+ .xpath(dataNode.getXpath())
+ .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves()))
+ .build();
}
- private static FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
- final AnchorEntity anchorEntity, final DataNode dataNode) {
- return FragmentEntity.builder()
- .dataspace(dataspaceEntity)
- .anchor(anchorEntity)
- .xpath(dataNode.getXpath())
- .attributes(GSON.toJson(dataNode.getLeaves()))
- .build();
+ @Override
+ public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
+ final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final String targetXpath = isRootXpath(xpath) ? xpath : CpsPathUtil.getNormalizedXpath(xpath);
+ final Collection<DataNode> dataNodes = getDataNodesForMultipleXpaths(dataspaceName, anchorName,
+ Collections.singletonList(targetXpath), fetchDescendantsOption);
+ if (dataNodes.isEmpty()) {
+ throw new DataNodeNotFoundException(dataspaceName, anchorName, xpath);
+ }
+ return dataNodes;
}
@Override
- public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
- final FetchDescendantsOption fetchDescendantsOption) {
- final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
- return toDataNode(fragmentEntity, fetchDescendantsOption);
+ public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
+ final Collection<String> xpaths,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(dataspaceName, anchorName, xpaths);
+ return toDataNodes(fragmentEntities, fetchDescendantsOption);
}
- private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
- final String xpath) {
+ private Collection<FragmentEntity> getFragmentEntities(final String dataspaceName, final String anchorName,
+ final Collection<String> xpaths) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+
+ final Collection<String> nonRootXpaths = new HashSet<>(xpaths);
+ final boolean haveRootXpath = nonRootXpaths.removeIf(CpsDataPersistenceServiceImpl::isRootXpath);
+
+ final Collection<String> normalizedXpaths = new HashSet<>(nonRootXpaths.size());
+ for (final String xpath : nonRootXpaths) {
+ try {
+ normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath));
+ } catch (final PathParsingException e) {
+ log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage());
+ }
+ }
+ final Collection<FragmentEntity> fragmentEntities =
+ new HashSet<>(fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorEntity.getId(), normalizedXpaths));
+
+ if (haveRootXpath) {
+ final List<FragmentExtract> fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity,
+ anchorEntity);
+ fragmentEntities.addAll(FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts));
+ }
+
+ return fragmentEntities;
+ }
+
+ private FragmentEntity getFragmentEntity(final String dataspaceName, final String anchorName, final String xpath) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+ final FragmentEntity fragmentEntity;
if (isRootXpath(xpath)) {
- return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity);
+ final List<FragmentExtract> fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity,
+ anchorEntity);
+ fragmentEntity = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts)
+ .stream().findFirst().orElse(null);
} else {
- return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity,
- xpath);
+ final String normalizedXpath = getNormalizedXpath(xpath);
+ fragmentEntity =
+ fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
+ }
+ if (fragmentEntity == null) {
+ throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath);
}
+ return fragmentEntity;
+
+ }
+
+ private Collection<FragmentEntity> buildFragmentEntitiesFromFragmentExtracts(final AnchorEntity anchorEntity,
+ final String normalizedXpath) {
+ final List<FragmentExtract> fragmentExtracts =
+ fragmentRepository.findByAnchorIdAndParentXpath(anchorEntity.getId(), normalizedXpath);
+ log.debug("Fetched {} fragment entities by anchor {} and cps path {}.",
+ fragmentExtracts.size(), anchorEntity.getName(), normalizedXpath);
+ return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
+
}
@Override
public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
- final FetchDescendantsOption fetchDescendantsOption) {
+ final FetchDescendantsOption fetchDescendantsOption) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
final CpsPathQuery cpsPathQuery;
try {
- cpsPathQuery = CpsPathQuery.createFrom(cpsPath);
- } catch (final IllegalStateException e) {
+ cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
+ } catch (final PathParsingException e) {
throw new CpsPathException(e.getMessage());
}
- List<FragmentEntity> fragmentEntities =
- fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
+
+ Collection<FragmentEntity> fragmentEntities;
+ if (canUseRegexQuickFind(fetchDescendantsOption, cpsPathQuery)) {
+ return getDataNodesUsingRegexQuickFind(fetchDescendantsOption, anchorEntity, cpsPathQuery);
+ }
+ fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
+ if (cpsPathQuery.hasAncestorAxis()) {
+ fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
+ }
+ return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities);
+ }
+
+ private static boolean canUseRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption,
+ final CpsPathQuery cpsPathQuery) {
+ return fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+ && !cpsPathQuery.hasLeafConditions()
+ && !cpsPathQuery.hasTextFunctionCondition();
+ }
+
+ private List<DataNode> getDataNodesUsingRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption,
+ final AnchorEntity anchorEntity,
+ final CpsPathQuery cpsPathQuery) {
+ Collection<FragmentEntity> fragmentEntities;
+ final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true);
+ final List<FragmentExtract> fragmentExtracts =
+ fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex);
+ fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
if (cpsPathQuery.hasAncestorAxis()) {
- final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
- fragmentEntities = ancestorXpaths.isEmpty()
- ? Collections.emptyList() : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
+ fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
+ }
+ return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
+ }
+
+ private Collection<FragmentEntity> getAncestorFragmentEntities(final int anchorId,
+ final CpsPathQuery cpsPathQuery,
+ final Collection<FragmentEntity> fragmentEntities) {
+ final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
+ return ancestorXpaths.isEmpty() ? Collections.emptyList()
+ : fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorId, ancestorXpaths);
+ }
+
+ private List<DataNode> createDataNodesFromProxiedFragmentEntities(
+ final FetchDescendantsOption fetchDescendantsOption,
+ final AnchorEntity anchorEntity,
+ final Collection<FragmentEntity> proxiedFragmentEntities) {
+ final List<DataNode> dataNodes = new ArrayList<>(proxiedFragmentEntities.size());
+ for (final FragmentEntity proxiedFragmentEntity : proxiedFragmentEntities) {
+ if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
+ dataNodes.add(toDataNode(proxiedFragmentEntity, fetchDescendantsOption));
+ } else {
+ final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath());
+ final Collection<FragmentEntity> unproxiedFragmentEntities =
+ buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath);
+ for (final FragmentEntity unproxiedFragmentEntity : unproxiedFragmentEntities) {
+ dataNodes.add(toDataNode(unproxiedFragmentEntity, fetchDescendantsOption));
+ }
+ }
}
- return fragmentEntities.stream()
- .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption))
- .collect(Collectors.toUnmodifiableList());
+ return Collections.unmodifiableList(dataNodes);
}
- private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
- final CpsPathQuery cpsPathQuery) {
+ private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption,
+ final Collection<FragmentEntity> fragmentEntities) {
+ final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
+ for (final FragmentEntity fragmentEntity : fragmentEntities) {
+ dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption));
+ }
+ return Collections.unmodifiableList(dataNodes);
+ }
+
+ private static String getNormalizedXpath(final String xpathSource) {
+ final String normalizedXpath;
+ try {
+ normalizedXpath = CpsPathUtil.getNormalizedXpath(xpathSource);
+ } catch (final PathParsingException e) {
+ throw new CpsPathException(e.getMessage());
+ }
+ return normalizedXpath;
+ }
+
+ @Override
+ public String startSession() {
+ return sessionManager.startSession();
+ }
+
+ @Override
+ public void closeSession(final String sessionId) {
+ sessionManager.closeSession(sessionId, SessionManager.WITH_COMMIT);
+ }
+
+ @Override
+ public void lockAnchor(final String sessionId, final String dataspaceName,
+ final String anchorName, final Long timeoutInMilliseconds) {
+ sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds);
+ }
+
+ private static Set<String> processAncestorXpath(final Collection<FragmentEntity> fragmentEntities,
+ final CpsPathQuery cpsPathQuery) {
final Set<String> ancestorXpath = new HashSet<>();
final Pattern pattern =
- Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
- + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*");
+ Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
+ + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*");
for (final FragmentEntity fragmentEntity : fragmentEntities) {
final Matcher matcher = pattern.matcher(fragmentEntity.getXpath());
if (matcher.matches()) {
}
private DataNode toDataNode(final FragmentEntity fragmentEntity,
- final FetchDescendantsOption fetchDescendantsOption) {
+ final FetchDescendantsOption fetchDescendantsOption) {
final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
- Map<String, Object> leaves = new HashMap<>();
+ Map<String, Serializable> leaves = new HashMap<>();
if (fragmentEntity.getAttributes() != null) {
- try {
- leaves = objectMapper.readValue(fragmentEntity.getAttributes(), Map.class);
- } catch (final JsonProcessingException jsonProcessingException) {
- final String message = "Parsing error occurred while processing fragmentEntity attributes.";
- log.error(message);
- throw new DataValidationException(message,
- jsonProcessingException.getMessage(), jsonProcessingException);
- }
+ leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
}
return new DataNodeBuilder()
- .withXpath(fragmentEntity.getXpath())
- .withLeaves(leaves)
- .withChildDataNodes(childDataNodes).build();
+ .withXpath(fragmentEntity.getXpath())
+ .withLeaves(leaves)
+ .withChildDataNodes(childDataNodes).build();
+ }
+
+ private Collection<DataNode> toDataNodes(final Collection<FragmentEntity> fragmentEntities,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Collection<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
+ for (final FragmentEntity fragmentEntity : fragmentEntities) {
+ dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption));
+ }
+ return dataNodes;
}
private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
- final FetchDescendantsOption fetchDescendantsOption) {
- if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) {
+ final FetchDescendantsOption fetchDescendantsOption) {
+ if (fetchDescendantsOption.hasNext()) {
return fragmentEntity.getChildFragments().stream()
- .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption))
- .collect(Collectors.toUnmodifiableList());
+ .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next()))
+ .collect(Collectors.toList());
}
return Collections.emptyList();
}
@Override
public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
- final Map<String, Object> leaves) {
- final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
- fragmentEntity.setAttributes(GSON.toJson(leaves));
+ final Map<String, Serializable> updateLeaves) {
+ final FragmentEntity fragmentEntity = getFragmentEntity(dataspaceName, anchorName, xpath);
+ final String currentLeavesAsString = fragmentEntity.getAttributes();
+ final String mergedLeaves = mergeLeaves(updateLeaves, currentLeavesAsString);
+ fragmentEntity.setAttributes(mergedLeaves);
fragmentRepository.save(fragmentEntity);
}
@Override
- public void replaceDataNodeTree(final String dataspaceName, final String anchorName, final DataNode dataNode) {
- final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath());
- replaceDataNodeTree(fragmentEntity, dataNode);
+ public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
+ final DataNode dataNode) {
+ final FragmentEntity fragmentEntity = getFragmentEntity(dataspaceName, anchorName, dataNode.getXpath());
+ updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
try {
fragmentRepository.save(fragmentEntity);
} catch (final StaleStateException staleStateException) {
throw new ConcurrencyException("Concurrent Transactions",
- String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
- dataspaceName, anchorName, dataNode.getXpath()),
- staleStateException);
+ String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
+ dataspaceName, anchorName, dataNode.getXpath()));
+ }
+ }
+
+ @Override
+ public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
+ final List<DataNode> updatedDataNodes) {
+ final Map<String, DataNode> xpathToUpdatedDataNode = updatedDataNodes.stream()
+ .collect(Collectors.toMap(DataNode::getXpath, dataNode -> dataNode));
+
+ final Collection<String> xpaths = xpathToUpdatedDataNode.keySet();
+ final Collection<FragmentEntity> existingFragmentEntities =
+ getFragmentEntities(dataspaceName, anchorName, xpaths);
+
+ for (final FragmentEntity existingFragmentEntity : existingFragmentEntities) {
+ final DataNode updatedDataNode = xpathToUpdatedDataNode.get(existingFragmentEntity.getXpath());
+ updateFragmentEntityAndDescendantsWithDataNode(existingFragmentEntity, updatedDataNode);
+ }
+
+ try {
+ fragmentRepository.saveAll(existingFragmentEntities);
+ } catch (final StaleStateException staleStateException) {
+ retryUpdateDataNodesIndividually(dataspaceName, anchorName, existingFragmentEntities);
+ }
+ }
+
+ private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
+ final Collection<FragmentEntity> fragmentEntities) {
+ final Collection<String> failedXpaths = new HashSet<>();
+
+ fragmentEntities.forEach(dataNodeFragment -> {
+ try {
+ fragmentRepository.save(dataNodeFragment);
+ } catch (final StaleStateException e) {
+ failedXpaths.add(dataNodeFragment.getXpath());
+ }
+ });
+
+ if (!failedXpaths.isEmpty()) {
+ final String failedXpathsConcatenated = String.join(",", failedXpaths);
+ throw new ConcurrencyException("Concurrent Transactions", String.format(
+ "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.",
+ failedXpathsConcatenated, dataspaceName, anchorName));
}
}
- private static void replaceDataNodeTree(final FragmentEntity existingFragmentEntity,
- final DataNode submittedDataNode) {
+ private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity,
+ final DataNode newDataNode) {
- existingFragmentEntity.setAttributes(GSON.toJson(submittedDataNode.getLeaves()));
+ existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
- final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments()
- .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
+ final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream()
+ .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
- final Collection updatedChildFragments = new HashSet<FragmentEntity>();
+ final Collection<FragmentEntity> updatedChildFragments = new HashSet<>();
- for (final DataNode submittedChildDataNode : submittedDataNode.getChildDataNodes()) {
+ for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) {
final FragmentEntity childFragment;
- if (isNewDataNode(submittedChildDataNode, existingChildrenByXpath)) {
+ if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) {
childFragment = convertToFragmentWithAllDescendants(
- existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), submittedChildDataNode);
+ existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild);
} else {
- childFragment = existingChildrenByXpath.get(submittedChildDataNode.getXpath());
- replaceDataNodeTree(childFragment, submittedChildDataNode);
+ childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath());
+ updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild);
}
updatedChildFragments.add(childFragment);
}
+
existingFragmentEntity.getChildFragments().clear();
existingFragmentEntity.getChildFragments().addAll(updatedChildFragments);
}
@Override
@Transactional
- public void replaceListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final Collection<DataNode> replacementDataNodes) {
- final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
- final String listNodeXpathPrefix = getListNodeXpathPrefix(replacementDataNodes);
+ public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> newListElements) {
+ final FragmentEntity parentEntity = getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
+ final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
- extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listNodeXpathPrefix);
- removeExistingListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
+ extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
+ deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
final Set<FragmentEntity> updatedChildFragmentEntities = new HashSet<>();
- for (final DataNode replacementDataNode : replacementDataNodes) {
- final FragmentEntity existingListNodeElementEntity =
- existingListElementFragmentEntitiesByXPath.get(replacementDataNode.getXpath());
- final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, replacementDataNode,
- existingListNodeElementEntity);
+ for (final DataNode newListElement : newListElements) {
+ final FragmentEntity existingListElementEntity =
+ existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath());
+ final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement,
+ existingListElementEntity);
updatedChildFragmentEntities.add(entityToBeAdded);
}
fragmentRepository.save(parentEntity);
}
- private static void removeExistingListElements(
- final Collection<FragmentEntity> fragmentEntities,
- final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath) {
+ @Override
+ @Transactional
+ public void deleteDataNodes(final String dataspaceName, final String anchorName) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName)
+ .ifPresent(
+ anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity)));
+ }
+
+ @Override
+ @Transactional
+ public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final Collection<AnchorEntity> anchorEntities =
+ anchorRepository.findAllByDataspaceAndNameIn(dataspaceEntity, anchorNames);
+ fragmentRepository.deleteByAnchorIn(anchorEntities);
+ }
+
+ @Override
+ @Transactional
+ public void deleteDataNodes(final String dataspaceName, final String anchorName,
+ final Collection<String> xpathsToDelete) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+
+ final Collection<String> normalizedXpaths = new ArrayList<>(xpathsToDelete.size());
+ for (final String xpath : xpathsToDelete) {
+ try {
+ normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath));
+ } catch (final PathParsingException e) {
+ log.debug("Error parsing xpath \"{}\": {}", xpath, e.getMessage());
+ }
+ }
+
+ fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), normalizedXpaths);
+ fragmentRepository.deleteListsByAnchorIdAndXpaths(anchorEntity.getId(), normalizedXpaths);
+ }
+
+ @Override
+ @Transactional
+ public void deleteListDataNode(final String dataspaceName, final String anchorName,
+ final String targetXpath) {
+ deleteDataNode(dataspaceName, anchorName, targetXpath, true);
+ }
+
+ @Override
+ @Transactional
+ public void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) {
+ deleteDataNode(dataspaceName, anchorName, targetXpath, false);
+ }
+
+ private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath,
+ final boolean onlySupportListNodeDeletion) {
+ final String parentNodeXpath;
+ FragmentEntity parentFragmentEntity = null;
+ boolean targetDeleted;
+ if (isRootXpath(targetXpath)) {
+ deleteDataNodes(dataspaceName, anchorName);
+ targetDeleted = true;
+ } else {
+ if (isRootContainerNodeXpath(targetXpath)) {
+ parentNodeXpath = targetXpath;
+ } else {
+ parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(targetXpath);
+ }
+ parentFragmentEntity = getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
+ if (CpsPathUtil.isPathToListElement(targetXpath)) {
+ targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
+ } else {
+ targetDeleted = deleteAllListElements(parentFragmentEntity, targetXpath);
+ final boolean tryToDeleteDataNode = !targetDeleted && !onlySupportListNodeDeletion;
+ if (tryToDeleteDataNode) {
+ targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
+ }
+ }
+ }
+ if (!targetDeleted) {
+ final String additionalInformation = onlySupportListNodeDeletion
+ ? "The target is probably not a List." : "";
+ throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(),
+ parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation);
+ }
+ }
+
+ private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) {
+ final String normalizedTargetXpath = CpsPathUtil.getNormalizedXpath(targetXpath);
+ if (parentFragmentEntity.getXpath().equals(normalizedTargetXpath)) {
+ fragmentRepository.deleteFragmentEntity(parentFragmentEntity.getId());
+ return true;
+ }
+ if (parentFragmentEntity.getChildFragments()
+ .removeIf(fragment -> fragment.getXpath().equals(normalizedTargetXpath))) {
+ fragmentRepository.save(parentFragmentEntity);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) {
+ final String normalizedListXpath = CpsPathUtil.getNormalizedXpath(listXpath);
+ final String deleteTargetXpathPrefix = normalizedListXpath + "[";
+ if (parentFragmentEntity.getChildFragments()
+ .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) {
+ fragmentRepository.save(parentFragmentEntity);
+ return true;
+ }
+ return false;
+ }
+
+ private static void deleteListElements(
+ final Collection<FragmentEntity> fragmentEntities,
+ final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath) {
fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values());
}
- private static String getListNodeXpathPrefix(final Collection<DataNode> replacementDataNodes) {
- final String firstChildNodeXpath = replacementDataNodes.iterator().next().getXpath();
- return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[") + 1);
+ private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
+ if (newListElements.isEmpty()) {
+ throw new CpsAdminException("Invalid list replacement",
+ "Cannot replace list elements with empty collection");
+ }
+ final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
+ return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1);
}
- private static FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
- final DataNode replacementDataNode,
- final FragmentEntity existingListNodeElementEntity) {
- if (existingListNodeElementEntity == null) {
+ private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
+ final DataNode newListElement,
+ final FragmentEntity existingListElementEntity) {
+ if (existingListElementEntity == null) {
return convertToFragmentWithAllDescendants(
- parentEntity.getDataspace(), parentEntity.getAnchor(), replacementDataNode);
+ parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement);
}
- if (replacementDataNode.getChildDataNodes().isEmpty()) {
- copyAttributesFromReplacementDataNode(existingListNodeElementEntity, replacementDataNode);
- existingListNodeElementEntity.getChildFragments().clear();
+ if (newListElement.getChildDataNodes().isEmpty()) {
+ copyAttributesFromNewListElement(existingListElementEntity, newListElement);
+ existingListElementEntity.getChildFragments().clear();
} else {
- replaceDataNodeTree(existingListNodeElementEntity, replacementDataNode);
+ updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement);
}
- return existingListNodeElementEntity;
+ return existingListElementEntity;
}
private static boolean isNewDataNode(final DataNode replacementDataNode,
- final Map<String, FragmentEntity> existingListNodeElementsByXpath) {
- return !existingListNodeElementsByXpath.containsKey(replacementDataNode.getXpath());
+ final Map<String, FragmentEntity> existingListElementsByXpath) {
+ return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
}
- private static void copyAttributesFromReplacementDataNode(final FragmentEntity existingListNodeElementEntity,
- final DataNode replacementDataNode) {
+ private static boolean isRootContainerNodeXpath(final String xpath) {
+ return 0 == xpath.lastIndexOf('/');
+ }
+
+ private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
+ final DataNode newListElement) {
final FragmentEntity replacementFragmentEntity =
- FragmentEntity.builder().attributes(GSON.toJson(replacementDataNode.getLeaves())).build();
- existingListNodeElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
+ FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString(
+ newListElement.getLeaves())).build();
+ existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
}
private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
- final Set<FragmentEntity> childEntities, final String listNodeXpathPrefix) {
+ final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
return childEntities.stream()
- .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listNodeXpathPrefix))
- .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
+ .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix))
+ .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
}
- @Override
- @Transactional
- public void deleteListDataNodes(final String dataspaceName, final String anchorName, final String listNodeXpath) {
- final String parentNodeXpath = listNodeXpath.substring(0, listNodeXpath.lastIndexOf('/'));
- final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
- final String descendantNode = listNodeXpath.substring(listNodeXpath.lastIndexOf('/'));
- final Matcher descendantNodeHasListNodeKey = Pattern.compile(REG_EX_FOR_LIST_NODE_KEY).matcher(descendantNode);
-
- final boolean xpathPointsToAValidChildNodeWithKey = parentEntity.getChildFragments().stream().anyMatch(
- fragment -> fragment.getXpath().equals(listNodeXpath));
-
- final boolean xpathPointsToAValidChildNodeWithoutKey = parentEntity.getChildFragments().stream().anyMatch(
- fragment -> fragment.getXpath().replaceAll(REG_EX_FOR_LIST_NODE_KEY, "").equals(listNodeXpath));
-
- if ((descendantNodeHasListNodeKey.find() && xpathPointsToAValidChildNodeWithKey)
- ||
- (!descendantNodeHasListNodeKey.find() && xpathPointsToAValidChildNodeWithoutKey)) {
- removeListNodeDescendants(parentEntity, listNodeXpath);
- } else {
- throw new DataNodeNotFoundException(parentEntity.getDataspace().getName(),
- parentEntity.getAnchor().getName(), listNodeXpath);
- }
+ private static boolean isRootXpath(final String xpath) {
+ return "/".equals(xpath) || "".equals(xpath);
}
- private void removeListNodeDescendants(final FragmentEntity parentFragmentEntity, final String listNodeXpath) {
- final Matcher descendantNodeHasListNodeKey = Pattern.compile(REG_EX_FOR_LIST_NODE_KEY).matcher(listNodeXpath);
- final String listNodeXpathPrefix = listNodeXpath + (descendantNodeHasListNodeKey.find() ? "" : "[");
- if (parentFragmentEntity.getChildFragments()
- .removeIf(fragment -> fragment.getXpath().startsWith(listNodeXpathPrefix))) {
- fragmentRepository.save(parentFragmentEntity);
+ private String mergeLeaves(final Map<String, Serializable> updateLeaves, final String currentLeavesAsString) {
+ final Map<String, Serializable> currentLeavesAsMap = currentLeavesAsString.isEmpty()
+ ? new HashMap<>() : jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class);
+ currentLeavesAsMap.putAll(updateLeaves);
+ if (currentLeavesAsMap.isEmpty()) {
+ return "";
}
- }
-
- private static boolean isRootXpath(final String xpath) {
- return "/".equals(xpath) || "".equals(xpath);
+ return jsonObjectMapper.asJsonString(currentLeavesAsMap);
}
}