CPS-314: Delete Dataspace

Issue-ID: CPS-314
Change-Id: I778e2b784c7b1ff3fecc1036425708dc4ec73227
Signed-off-by: niamhcore <niamh.core@est.tech>
diff --git a/cps-rest/docs/openapi/cpsAdmin.yml b/cps-rest/docs/openapi/cpsAdmin.yml
index a022ef1..869cb6e 100644
--- a/cps-rest/docs/openapi/cpsAdmin.yml
+++ b/cps-rest/docs/openapi/cpsAdmin.yml
@@ -35,6 +35,26 @@
       '403':
         $ref: 'components.yml#/components/responses/Forbidden'
 
+  delete:
+    description: Delete a dataspace
+    tags:
+      - cps-admin
+    summary: Delete a dataspace
+    operationId: deleteDataspace
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery'
+    responses:
+      '204':
+        $ref: 'components.yml#/components/responses/NoContent'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '401':
+        $ref: 'components.yml#/components/responses/Unauthorized'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '409':
+        $ref: 'components.yml#/components/responses/Conflict'
+
 schemaSet:
   post:
     description: Create a new schema set in the given dataspace
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java
index 55fdbbe..52e64a9 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java
@@ -71,6 +71,18 @@
     }
 
     /**
+     * Delete a dataspace.
+     *
+     * @param dataspaceName name of dataspace to be deleted
+     * @return a {@Link ResponseEntity} of {@link HttpStatus} NO_CONTENT
+     */
+    @Override
+    public ResponseEntity<Void> deleteDataspace(final String dataspaceName) {
+        cpsAdminService.deleteDataspace(dataspaceName);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    /**
      * Create a {@link SchemaSet}.
      *
      * @param multipartFile multipart file
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
index 84da2db..e8cfcfb 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
@@ -311,6 +311,18 @@
             response.status == HttpStatus.NO_CONTENT.value()
     }
 
+    def 'Delete dataspace.'() {
+        given: 'an endpoint'
+            def dataspaceEndpoint = "$basePath/v1/dataspaces"
+        when: 'delete dataspace endpoint is invoked'
+            def response = mvc.perform(delete(dataspaceEndpoint)
+                .param('dataspace-name', dataspaceName))
+                .andReturn().response
+        then: 'associated service method is invoked with expected parameter'
+            1 * mockCpsAdminService.deleteDataspace(dataspaceName)
+        and: 'response code indicates success'
+            response.status == HttpStatus.NO_CONTENT.value()
+    }
 
     def createMultipartFile(filename, content) {
         return new MockMultipartFile("file", filename, "text/plain", content.getBytes())
@@ -333,4 +345,5 @@
         multipartFile.getInputStream() >> { throw new IOException() }
         return multipartFile
     }
+
 }
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
index 079a59c..f596844 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
@@ -37,6 +37,7 @@
 import org.onap.cps.spi.exceptions.ModelValidationException
 import org.onap.cps.spi.exceptions.NotFoundInDataspaceException
 import org.onap.cps.spi.exceptions.SchemaSetInUseException
+import org.onap.cps.spi.exceptions.DataspaceInUseException
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
@@ -145,7 +146,8 @@
             assertTestResponse(response, CONFLICT, exceptionThrown.getMessage(), exceptionThrown.getDetails())
         where: 'the following exceptions are thrown'
             exceptionThrown << [new DataInUseException(dataspaceName, existingObjectName),
-                                new SchemaSetInUseException(dataspaceName, existingObjectName)]
+                                new SchemaSetInUseException(dataspaceName, existingObjectName),
+                                new DataspaceInUseException(dataspaceName, errorDetails)]
     }
 
     /*
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
index b1bd03c..9c69006 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
@@ -32,6 +32,7 @@
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.YangResourceModuleReference;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.DataspaceInUseException;
 import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.repository.AnchorRepository;
@@ -71,6 +72,22 @@
     }
 
     @Override
+    public void deleteDataspace(final String dataspaceName) {
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        final int numberOfAssociatedAnchors = anchorRepository.countByDataspace(dataspaceEntity);
+        if (numberOfAssociatedAnchors != 0) {
+            throw new DataspaceInUseException(dataspaceName,
+                    String.format("Dataspace contains %d anchor(s)", numberOfAssociatedAnchors));
+        }
+        final int numberOfAssociatedSchemaSets = schemaSetRepository.countByDataspace(dataspaceEntity);
+        if (numberOfAssociatedSchemaSets != 0) {
+            throw new DataspaceInUseException(dataspaceName,
+                    String.format("Dataspace contains %d schemaset(s)", numberOfAssociatedSchemaSets));
+        }
+        dataspaceRepository.delete(dataspaceEntity);
+    }
+
+    @Override
     public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
         final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final var schemaSetEntity =
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
index 5870fd9..471f175 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
@@ -45,6 +45,8 @@
 
     Collection<AnchorEntity> findAllBySchemaSet(@NotNull SchemaSetEntity schemaSetEntity);
 
+    Integer countByDataspace(@NotNull DataspaceEntity dataspaceEntity);
+
     @Query(value = "SELECT anchor.* FROM yang_resource\n"
         + "JOIN schema_set_yang_resources ON schema_set_yang_resources.yang_resource_id = yang_resource.id\n"
         + "JOIN schema_set ON schema_set.id = schema_set_yang_resources.schema_set_id\n"
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java
index 7b56f93..a15ce62 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java
@@ -19,7 +19,6 @@
 
 package org.onap.cps.spi.repository;
 
-import java.util.List;
 import java.util.Optional;
 import javax.validation.constraints.NotNull;
 import org.onap.cps.spi.entities.DataspaceEntity;
@@ -31,11 +30,11 @@
 @Repository
 public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Integer> {
 
-    List<SchemaSetEntity> findAllByDataspace(@NotNull DataspaceEntity dataspaceEntity);
-
     Optional<SchemaSetEntity> findByDataspaceAndName(@NotNull DataspaceEntity dataspaceEntity,
         @NotNull String schemaSetName);
 
+    Integer countByDataspace(@NotNull DataspaceEntity dataspaceEntity);
+
     /**
      * Gets a schema set by dataspace and schema set name.
      *
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
index a0df2b1..4b5b116 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
@@ -24,6 +24,7 @@
 import org.onap.cps.spi.CpsAdminPersistenceService
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.AnchorNotFoundException
+import org.onap.cps.spi.exceptions.DataspaceInUseException
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException
@@ -36,10 +37,9 @@
     @Autowired
     CpsAdminPersistenceService objectUnderTest
 
-
     static final String SET_DATA = '/data/anchor.sql'
     static final String SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES = '/data/anchors-schemaset-modules.sql'
-    static final String EMPTY_DATASPACE_NAME = 'DATASPACE-002'
+    static final String DATASPACE_WITH_NO_DATA = 'DATASPACE-002'
     static final Integer DELETED_ANCHOR_ID = 3001
     static final Long DELETED_FRAGMENT_ID = 4001
 
@@ -111,7 +111,7 @@
             dataspaceName        || expectedAnchors
             DATASPACE_NAME       || [Anchor.builder().name(ANCHOR_NAME1).schemaSetName(SCHEMA_SET_NAME1).dataspaceName(DATASPACE_NAME).build(),
                                      Anchor.builder().name(ANCHOR_NAME2).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build()]
-            EMPTY_DATASPACE_NAME || []
+            DATASPACE_WITH_NO_DATA || []
     }
 
     @Sql(CLEAR_DATA)
@@ -173,4 +173,27 @@
     def buildAnchor(def anchorName, def dataspaceName, def SchemaSetName) {
         return Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(SchemaSetName).build()
     }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete dataspace.'() {
+        when: 'delete dataspace action is invoked'
+            objectUnderTest.deleteDataspace(DATASPACE_WITH_NO_DATA)
+        then: 'dataspace is deleted'
+            assert dataspaceRepository.findByName(DATASPACE_WITH_NO_DATA).isEmpty();
+    }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete dataspace when #scenario.'() {
+        when: 'delete dataspace action is invoked'
+            objectUnderTest.deleteDataspace(dataspaceName)
+        then: 'the correct exception is thrown with the relevant details'
+            def thrownException = thrown(expectedException)
+            thrownException.details.contains(expectedMessageDetails)
+        where: 'the following data is used'
+            scenario                          | dataspaceName       || expectedException            | expectedMessageDetails
+            'dataspace name does not exist'   | 'unknown'           || DataspaceNotFoundException   | 'unknown does not exist'
+            'dataspace contains an anchor'    | 'DATASPACE-001'     || DataspaceInUseException      | 'contains 2 anchor(s)'
+            'dataspace contains schemasets'   | 'DATASPACE-003'     || DataspaceInUseException      | 'contains 1 schemaset(s)'
+    }
+
 }
diff --git a/cps-ri/src/test/resources/data/anchor.sql b/cps-ri/src/test/resources/data/anchor.sql
index dbf1a6a..c9240f7 100644
--- a/cps-ri/src/test/resources/data/anchor.sql
+++ b/cps-ri/src/test/resources/data/anchor.sql
@@ -21,10 +21,14 @@
 */
 
 INSERT INTO DATASPACE (ID, NAME) VALUES
-    (1001, 'DATASPACE-001'), (1002, 'DATASPACE-002');
+    (1001, 'DATASPACE-001'),
+    (1002, 'DATASPACE-002'),
+    (1003, 'DATASPACE-003');
 
 INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES
-    (2001, 'SCHEMA-SET-001', 1001), (2002, 'SCHEMA-SET-002', 1001);
+    (2001, 'SCHEMA-SET-001', 1001),
+    (2002, 'SCHEMA-SET-002', 1001),
+    (2003, 'SCHEMA-SET-002', 1003);
 
 INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
     (3001, 'ANCHOR-001', 1001, 2001),
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
index 1d08cde..7ba9599 100755
--- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
@@ -42,6 +42,13 @@
     void createDataspace(@NonNull String dataspaceName);
 
     /**
+     * Delete dataspace.
+     *
+     * @param dataspaceName the name of the dataspace to delete
+     */
+    void deleteDataspace(@NonNull String dataspaceName);
+
+    /**
      * Create an Anchor.
      *
      * @param dataspaceName dataspace name
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
index faff7b6..d831793 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
@@ -42,6 +42,11 @@
     }
 
     @Override
+    public void deleteDataspace(final String dataspaceName) {
+        cpsAdminPersistenceService.deleteDataspace(dataspaceName);
+    }
+
+    @Override
     public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
         cpsAdminPersistenceService.createAnchor(dataspaceName, schemaSetName, anchorName);
     }
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
index 104ac4f..9553700 100755
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
@@ -41,6 +41,13 @@
     void createDataspace(@NonNull String dataspaceName);
 
     /**
+     * Delete dataspace.
+     *
+     * @param dataspaceName the name of the dataspace to delete
+     */
+    void deleteDataspace(@NonNull String dataspaceName);
+
+    /**
      * Create an Anchor.
      *
      * @param dataspaceName dataspace name
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataspaceInUseException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataspaceInUseException.java
new file mode 100644
index 0000000..7889301
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataspaceInUseException.java
@@ -0,0 +1,40 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  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.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+/**
+ * Runtime exception.
+ * Thrown when given dataspace name is rejected to be deleted because it has anchor or schemasets associated.
+ */
+
+public class DataspaceInUseException extends DataInUseException {
+
+    private static final long serialVersionUID = 4531370947720760347L;
+
+    /**
+     * Constructor.
+     *
+     * @param dataspaceName dataspace name
+     * @param details error message details
+     */
+    public DataspaceInUseException(final String dataspaceName, final String details) {
+        super(String.format("Dataspace with name %s is being used.", dataspaceName), details);
+    }
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
index 95afeb4..6d1f586 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
@@ -78,4 +78,12 @@
             objectUnderTest.queryAnchorNames('some-dataspace-name', ['some-module-name']) == ['some-anchor-identifier']
 
     }
+
+    def 'Delete dataspace.'() {
+        when: 'delete dataspace is invoked'
+            objectUnderTest.deleteDataspace('someDataspace')
+        then: 'associated persistence service method is invoked with correct parameter'
+            1 * mockCpsAdminPersistenceService.deleteDataspace('someDataspace')
+    }
+
 }
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy
index 4243c18..5bd3678 100755
--- a/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy
@@ -170,4 +170,12 @@
         expect: 'the exception has the provided details'
             exception.details == providedDetails
     }
+
+    def 'Creating an exception that the dataspace is being used and cannot be deleted.'() {
+        given: 'a dataspace in use exception is created'
+            def exception = new DataspaceInUseException(dataspaceName,providedDetails)
+        expect: 'the exception has the correct message with dataspace name and provided details'
+            exception.message == "Dataspace with name ${dataspaceName} is being used."
+            exception.details == providedDetails
+    }
 }