/*
- * ============LICENSE_START=======================================================
+ * ============LICENSE_START=======================================================
* Copyright (C) 2021 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2020-2021 Bell Canada.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
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.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
+import lombok.extern.slf4j.Slf4j;
+import org.hibernate.StaleStateException;
import org.onap.cps.cpspath.parser.CpsPathQuery;
-import org.onap.cps.cpspath.parser.CpsPathQueryType;
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.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.ConcurrencyException;
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.FragmentRepository;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
@Service
+@Slf4j
public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
- @Autowired
private DataspaceRepository dataspaceRepository;
- @Autowired
private AnchorRepository anchorRepository;
- @Autowired
private FragmentRepository fragmentRepository;
+ private final ObjectMapper objectMapper;
+
+ /**
+ * 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();
+ }
+
private static final Gson GSON = new GsonBuilder().create();
- private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@\\S+?]){0,1})";
+ private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
+ private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE =
+ Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
@Override
public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath,
final DataNode dataNode) {
final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentXpath);
- final var fragmentEntity =
+ final FragmentEntity fragmentEntity =
toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode);
parentFragment.getChildFragments().add(fragmentEntity);
try {
}
@Override
- public void addListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> dataNodes) {
final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
final List<FragmentEntity> newFragmentEntities =
parentFragment.getChildFragments().addAll(newFragmentEntities);
try {
fragmentRepository.save(parentFragment);
+ dataNodes.forEach(
+ dataNode -> getChildFragments(dataspaceName, anchorName, dataNode)
+ );
} catch (final DataIntegrityViolationException exception) {
final List<String> conflictXpaths = dataNodes.stream()
.map(DataNode::getXpath)
@Override
public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
- final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
- final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
- final var fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+ final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
dataNode);
try {
fragmentRepository.save(fragmentEntity);
*/
private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) {
- final var parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
+ final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
final FragmentEntity childFragment =
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 static FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
final AnchorEntity anchorEntity, final DataNode dataNode) {
return FragmentEntity.builder()
@Override
public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
final FetchDescendantsOption fetchDescendantsOption) {
- final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
+ final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
return toDataNode(fragmentEntity, fetchDescendantsOption);
}
private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
final String xpath) {
- final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
- final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
if (isRootXpath(xpath)) {
- return fragmentRepository.getFirstByDataspaceAndAnchor(dataspaceEntity, anchorEntity);
+ return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity);
} else {
return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity,
xpath);
@Override
public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
final FetchDescendantsOption fetchDescendantsOption) {
- final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
- final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+ 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) {
throw new CpsPathException(e.getMessage());
}
- List<FragmentEntity> fragmentEntities;
- if (CpsPathQueryType.XPATH_LEAF_VALUE.equals(cpsPathQuery.getCpsPathQueryType())) {
- fragmentEntities = fragmentRepository
- .getByAnchorAndXpathAndLeafAttributes(anchorEntity.getId(), cpsPathQuery.getXpathPrefix(),
- cpsPathQuery.getLeafName(), cpsPathQuery.getLeafValue());
- } else if (CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES.equals(cpsPathQuery.getCpsPathQueryType())) {
- final String leafDataAsJson = GSON.toJson(cpsPathQuery.getLeavesData());
- fragmentEntities = fragmentRepository
- .getByAnchorAndDescendentNameAndLeafValues(anchorEntity.getId(), cpsPathQuery.getDescendantName(),
- leafDataAsJson);
- } else {
- fragmentEntities = fragmentRepository
- .getByAnchorAndXpathEndsInDescendantName(anchorEntity.getId(), cpsPathQuery.getDescendantName());
- }
+ List<FragmentEntity> fragmentEntities =
+ fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
if (cpsPathQuery.hasAncestorAxis()) {
final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
fragmentEntities = ancestorXpaths.isEmpty()
private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
final CpsPathQuery cpsPathQuery) {
final Set<String> ancestorXpath = new HashSet<>();
- final var pattern =
- Pattern.compile("(\\S*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
- + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/\\S*");
+ final Pattern pattern =
+ Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier())
+ + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*");
for (final FragmentEntity fragmentEntity : fragmentEntities) {
- final var matcher = pattern.matcher(fragmentEntity.getXpath());
+ final Matcher matcher = pattern.matcher(fragmentEntity.getXpath());
if (matcher.matches()) {
ancestorXpath.add(matcher.group(1));
}
return ancestorXpath;
}
- private static DataNode toDataNode(final FragmentEntity fragmentEntity,
+ private DataNode toDataNode(final FragmentEntity fragmentEntity,
final FetchDescendantsOption fetchDescendantsOption) {
- final Map<String, Object> leaves = GSON.fromJson(fragmentEntity.getAttributes(), Map.class);
final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
+ Map<String, Object> 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);
+ }
+ }
return new DataNodeBuilder()
.withXpath(fragmentEntity.getXpath())
.withLeaves(leaves)
.withChildDataNodes(childDataNodes).build();
}
- private static List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
+ private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
final FetchDescendantsOption fetchDescendantsOption) {
if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) {
return fragmentEntity.getChildFragments().stream()
@Override
public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
final Map<String, Object> leaves) {
- final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
+ final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
fragmentEntity.setAttributes(GSON.toJson(leaves));
fragmentRepository.save(fragmentEntity);
}
@Override
public void replaceDataNodeTree(final String dataspaceName, final String anchorName, final DataNode dataNode) {
- final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath());
- removeExistingDescendants(fragmentEntity);
+ final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath());
+ replaceDataNodeTree(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);
+ }
+ }
- fragmentEntity.setAttributes(GSON.toJson(dataNode.getLeaves()));
- final Set<FragmentEntity> childFragmentEntities = dataNode.getChildDataNodes().stream().map(
- childDataNode -> convertToFragmentWithAllDescendants(
- fragmentEntity.getDataspace(), fragmentEntity.getAnchor(), childDataNode)
- ).collect(Collectors.toUnmodifiableSet());
- fragmentEntity.setChildFragments(childFragmentEntities);
+ private static void replaceDataNodeTree(final FragmentEntity existingFragmentEntity,
+ final DataNode newDataNode) {
- fragmentRepository.save(fragmentEntity);
+ existingFragmentEntity.setAttributes(GSON.toJson(newDataNode.getLeaves()));
+
+ final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments()
+ .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
+
+ final Collection<FragmentEntity> updatedChildFragments = new HashSet<>();
+
+ for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) {
+ final FragmentEntity childFragment;
+ if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) {
+ childFragment = convertToFragmentWithAllDescendants(
+ existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild);
+ } else {
+ childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath());
+ replaceDataNodeTree(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> dataNodes) {
- final var parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
- final var firstChildNodeXpath = dataNodes.iterator().next().getXpath();
- final var listNodeXpath = firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("["));
- removeListNodeDescendants(parentEntity, listNodeXpath);
- final Set<FragmentEntity> childFragmentEntities = dataNodes.stream().map(
- dataNode -> convertToFragmentWithAllDescendants(
- parentEntity.getDataspace(), parentEntity.getAnchor(), dataNode)
- ).collect(Collectors.toUnmodifiableSet());
- parentEntity.getChildFragments().addAll(childFragmentEntities);
+ public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> newListElements) {
+ final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
+ final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
+ extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
+ deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
+ final Set<FragmentEntity> updatedChildFragmentEntities = new HashSet<>();
+ for (final DataNode newListElement : newListElements) {
+ final FragmentEntity existingListElementEntity =
+ existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath());
+ final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement,
+ existingListElementEntity);
+
+ updatedChildFragmentEntities.add(entityToBeAdded);
+ }
+ parentEntity.getChildFragments().addAll(updatedChildFragmentEntities);
fragmentRepository.save(parentEntity);
}
- private void removeListNodeDescendants(final FragmentEntity parentFragmentEntity, final String listNodeXpath) {
- final String listNodeXpathPrefix = listNodeXpath + "[";
+
+ @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 = targetXpath.substring(0, targetXpath.lastIndexOf('/'));
+ final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/'));
+ final boolean isListElement = REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE.matcher(lastXpathElement).find();
+ boolean targetExist;
+ if (isListElement) {
+ targetExist = deleteDataNode(parentFragmentEntity, targetXpath);
+ } else {
+ targetExist = deleteAllListElements(parentFragmentEntity, targetXpath);
+ final boolean tryToDeleteDataNode = !targetExist && !onlySupportListNodeDeletion;
+ if (tryToDeleteDataNode) {
+ targetExist = deleteDataNode(parentFragmentEntity, targetXpath);
+ }
+ }
+ if (!targetExist) {
+ 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) {
if (parentFragmentEntity.getChildFragments()
- .removeIf(fragment -> fragment.getXpath().startsWith(listNodeXpathPrefix))) {
+ .removeIf(fragment -> fragment.getXpath().equals(targetXpath))) {
fragmentRepository.save(parentFragmentEntity);
+ return true;
}
+ return false;
}
- private void removeExistingDescendants(final FragmentEntity fragmentEntity) {
- fragmentEntity.setChildFragments(Collections.emptySet());
- fragmentRepository.save(fragmentEntity);
+
+ private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) {
+ final String deleteTargetXpathPrefix = listXpath + "[";
+ 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 getListElementXpathPrefix(final Collection<DataNode> newListElements) {
+ final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
+ return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[") + 1);
+ }
+
+ private static FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
+ final DataNode newListElement,
+ final FragmentEntity existingListElementEntity) {
+ if (existingListElementEntity == null) {
+ return convertToFragmentWithAllDescendants(
+ parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement);
+ }
+ if (newListElement.getChildDataNodes().isEmpty()) {
+ copyAttributesFromNewListElement(existingListElementEntity, newListElement);
+ existingListElementEntity.getChildFragments().clear();
+ } else {
+ replaceDataNodeTree(existingListElementEntity, newListElement);
+ }
+ return existingListElementEntity;
+ }
+
+ private static boolean isNewDataNode(final DataNode replacementDataNode,
+ final Map<String, FragmentEntity> existingListElementsByXpath) {
+ return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
+ }
+
+ private static void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
+ final DataNode newListElement) {
+ final FragmentEntity replacementFragmentEntity =
+ FragmentEntity.builder().attributes(GSON.toJson(newListElement.getLeaves())).build();
+ existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
+ }
+
+ private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
+ final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
+ return childEntities.stream()
+ .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix))
+ .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
}
private static boolean isRootXpath(final String xpath) {