@Override
public ResponseEntity<Void> cleanDataspace(final String apiVersion, final String dataspaceName) {
cpsModuleService.deleteAllUnusedYangModuleData(dataspaceName);
+ cpsDataspaceService.deleteAllOrphanedData(dataspaceName);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2025 Nordix Foundation.
+ * Copyright (C) 2020-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 TechMahindra Ltd.
import jakarta.transaction.Transactional;
import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.ri.models.SchemaSetEntity;
import org.onap.cps.ri.repository.AnchorRepository;
import org.onap.cps.ri.repository.DataspaceRepository;
+import org.onap.cps.ri.repository.FragmentRepository;
import org.onap.cps.ri.repository.SchemaSetRepository;
import org.onap.cps.spi.CpsAdminPersistenceService;
import org.springframework.dao.DataIntegrityViolationException;
private final DataspaceRepository dataspaceRepository;
private final AnchorRepository anchorRepository;
private final SchemaSetRepository schemaSetRepository;
+ private final FragmentRepository fragmentRepository;
@Override
public void createDataspace(final String dataspaceName) {
anchorRepository.updateAnchorSchemaSetId(schemaSetEntity.getId(), anchorEntity.getId());
}
+ @Override
+ public void deleteAllOrphanedFragmentEntities(final String dataspaceName) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final List<AnchorEntity> anchorEntities = anchorRepository.getAnchorEntitiesByDataspace(dataspaceEntity);
+ final Set<Long> anchorIds = new HashSet<>();
+ for (final AnchorEntity anchorEntity : anchorEntities) {
+ anchorIds.add(anchorEntity.getId());
+ }
+ fragmentRepository.deleteOrphanedFragmentEntities(anchorIds);
+ }
+
private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2021-2024 Nordix Foundation
+ * Modifications Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
* ================================================================================
* 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.ri.repository;
import java.util.Collection;
+import java.util.List;
import java.util.Optional;
import org.onap.cps.api.exceptions.AnchorNotFoundException;
import org.onap.cps.ri.models.AnchorEntity;
@Query(value = "UPDATE anchor SET schema_set_id =:schemaSetId WHERE id = :anchorId ", nativeQuery = true)
void updateAnchorSchemaSetId(@Param("schemaSetId") int schemaSetId, @Param("anchorId") long anchorId);
+ List<AnchorEntity> getAnchorEntitiesByDataspace(DataspaceEntity dataspaceEntity);
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation.
+ * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2020-2021 Bell Canada.
* Modifications Copyright (C) 2020-2021 Pantheon.tech.
* Modifications Copyright (C) 2023 TechMahindra Ltd.
package org.onap.cps.ri.repository;
+import jakarta.transaction.Transactional;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import org.onap.cps.ri.models.AnchorEntity;
import org.onap.cps.ri.models.FragmentEntity;
import org.onap.cps.ri.utils.EscapeUtils;
@Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND parent_id IS NULL", nativeQuery = true)
List<FragmentEntity> findRootsByAnchorId(@Param("anchorId") long anchorId);
+ @Modifying
+ @Transactional
+ @Query(value = "DELETE FROM fragment where anchor_id IN (:anchorIds) "
+ + "AND ("
+ + "parent_id in"
+ + " (SELECT id from fragment WHERE (LENGTH(xpath) - LENGTH(REPLACE(xpath, '/','')) > 1)"
+ + " AND parent_id is null)"
+ + " OR ((LENGTH(xpath) - LENGTH(REPLACE(xpath, '/', '')) > 1) AND parent_id is null)"
+ + ")", nativeQuery = true)
+ void deleteOrphanedFragmentEntities(@Param("anchorIds") Set<Long> anchorIds);
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2023 Nordix Foundation
+ * Copyright (C) 2020-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 TechMahindra Ltd.
*/
Collection<Dataspace> getAllDataspaces();
+ /**
+ * Delete orphaned data for a given dataspace name.
+ *
+ * @param dataspaceName the name of the dataspace where the data is located.
+ */
+ void deleteAllOrphanedData(String dataspaceName);
+
}
return cpsAdminPersistenceService.getAllDataspaces();
}
+ @Override
+ public void deleteAllOrphanedData(final String dataspaceName) {
+ cpsValidator.validateNameCharacters(dataspaceName);
+ cpsAdminPersistenceService.deleteAllOrphanedFragmentEntities(dataspaceName);
+ }
}
* @param schemaSetName schema set name
*/
void updateAnchorSchemaSet(String dataspaceName, String anchorName, String schemaSetName);
+
+ /**
+ * Delete all fragment entities that have no parent.
+ *
+ * @param dataspaceName dataspace name
+ */
+ void deleteAllOrphanedFragmentEntities(String dataspaceName);
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation
+ * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
1 * mockCpsValidator.validateNameCharacters('someDataspace')
}
+ def 'Delete all orphaned data.'(){
+ when: 'deleting all orphaned data'
+ objectUnderTest.deleteAllOrphanedData('some-dataspaceName')
+ then: 'the persistence service method to delete all orphaned fragment entities is invoked'
+ 1 * mockCpsAdminPersistenceService.deleteAllOrphanedFragmentEntities('some-dataspaceName')
+ }
+
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2023-2025 Nordix Foundation
+ * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved.
* ================================================================================
* 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.integration.functional.cps
+import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION
+
import org.onap.cps.api.CpsDataspaceService
+import java.time.OffsetDateTime
+import org.onap.cps.api.exceptions.DataNodeNotFoundException
import org.onap.cps.integration.base.FunctionalSpecBase
import org.onap.cps.api.exceptions.AlreadyDefinedException
import org.onap.cps.api.exceptions.DataspaceInUseException
import org.onap.cps.api.exceptions.DataspaceNotFoundException
+import org.onap.cps.ri.repository.FragmentRepository
+import org.onap.cps.utils.ContentType
class DataspaceServiceIntegrationSpec extends FunctionalSpecBase {
thrown(AlreadyDefinedException)
}
+ def 'Delete all orphaned data in a dataspace.'() {
+ setup: 'an anchor'
+ cpsAnchorService.createAnchor(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'testAnchor')
+ and: 'orphaned data'
+ def jsonDataMap = [:]
+ jsonDataMap.put('/bookstore/categories[@code=\'3\']', '{"books":[{"title": "Matilda"}]}')
+ jsonDataMap.put('/bookstore/categories[@code=\'3\']', '{"sub-categories":{"code":"1","additional-info":{"info-name":"sample"}}}')
+ cpsDataService.updateDataNodesAndDescendants(GENERAL_TEST_DATASPACE, 'testAnchor', jsonDataMap, OffsetDateTime.now(), ContentType.JSON)
+ def dataNodes = cpsDataService.getDataNodes(GENERAL_TEST_DATASPACE, 'testAnchor','/', INCLUDE_ALL_DESCENDANTS)
+ assert dataNodes.size() == 1
+ assert dataNodes.childDataNodes.size() == 1
+ and: 'parent node does not exist'
+ assert cpsQueryService.queryDataNodesAcrossAnchors(GENERAL_TEST_DATASPACE, '/bookstore', INCLUDE_ALL_DESCENDANTS, NO_PAGINATION).size() == 0
+ when: 'deleting all orphaned data in a dataspace'
+ objectUnderTest.deleteAllOrphanedData(GENERAL_TEST_DATASPACE)
+ and: 'get data nodes in dataspace'
+ cpsDataService.getDataNodes(GENERAL_TEST_DATASPACE, 'testAnchor','/', INCLUDE_ALL_DESCENDANTS)
+ then: 'there will be no more data nodes available'
+ thrown(DataNodeNotFoundException)
+ cleanup: 'remove the data for this test'
+ cpsAnchorService.deleteAnchor(GENERAL_TEST_DATASPACE, 'testAnchor')
+ }
+
}
type string;
}
+ container sub-categories {
+ leaf code {
+ type string;
+ }
+ container additional-info {
+ leaf info-name {
+ type string;
+ }
+ }
+ }
+
list books {
key title;