+
+ 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();
+ }
+
+ @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);
+ }
+
+ private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
+ final String xpath) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+ if (isRootXpath(xpath)) {
+ 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 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 =
+ fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
+ if (cpsPathQuery.hasAncestorAxis()) {
+ final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
+ fragmentEntities = ancestorXpaths.isEmpty()
+ ? Collections.emptyList() : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
+ }
+ return fragmentEntities.stream()
+ .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ private static Set<String> processAncestorXpath(final List<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]*");
+ for (final FragmentEntity fragmentEntity : fragmentEntities) {
+ final Matcher matcher = pattern.matcher(fragmentEntity.getXpath());
+ if (matcher.matches()) {
+ ancestorXpath.add(matcher.group(1));
+ }
+ }
+ return ancestorXpath;
+ }
+
+ private DataNode toDataNode(final FragmentEntity fragmentEntity,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
+ Map<String, Object> leaves = new HashMap<>();
+ if (fragmentEntity.getAttributes() != null) {
+ leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
+ }
+ return new DataNodeBuilder()
+ .withXpath(fragmentEntity.getXpath())
+ .withLeaves(leaves)
+ .withChildDataNodes(childDataNodes).build();
+ }
+
+ private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) {
+ return fragmentEntity.getChildFragments().stream()
+ .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption))
+ .collect(Collectors.toUnmodifiableList());
+ }
+ 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(jsonObjectMapper.asJsonString(leaves));
+ 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);
+ 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);
+ }
+ }
+
+ private void replaceDataNodeTree(final FragmentEntity existingFragmentEntity,
+ final DataNode newDataNode) {
+
+ existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(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 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);
+ }
+
+
+ @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().equals(targetXpath))) {
+ fragmentRepository.save(parentFragmentEntity);
+ return true;
+ }
+ return false;
+ }
+
+
+ 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 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 void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
+ final DataNode newListElement) {
+ final FragmentEntity replacementFragmentEntity =
+ FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString(
+ 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) {
+ return "/".equals(xpath) || "".equals(xpath);
+ }