Decouple anchor from fragment in persistence module

Issue-ID: CPS-161
Change-Id: Ia446b26ee4eca9281e86bd2be3dd6836aa201597
Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java
new file mode 100644
index 0000000..e7e9c97
--- /dev/null
+++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java
@@ -0,0 +1,70 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Pantheon.tech
+ *  ================================================================================
+ *  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.entities;
+
+import java.io.Serializable;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Entity to store an anchor.
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@Entity
+@Table(name = "anchor")
+public class AnchorEntity implements Serializable {
+
+    private static final long serialVersionUID = -8049987915308262518L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    @NotNull
+    @Column
+    private String name;
+
+    @NotNull
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "schema_set_id")
+    private SchemaSet schemaSet;
+
+    @NotNull
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "dataspace_id")
+    private Dataspace dataspace;
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/Fragment.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/Fragment.java
index 2dbd6e9..053a223 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/entities/Fragment.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/Fragment.java
@@ -46,10 +46,10 @@
  */

 @Getter

 @Setter

-@Entity

 @AllArgsConstructor

 @NoArgsConstructor

 @Builder

+@Entity

 @TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)})

 public class Fragment implements Serializable {

 

@@ -67,9 +67,6 @@
     @Column(columnDefinition = "jsonb")

     private String attributes;

 

-    @Column(columnDefinition = "text")

-    private String anchorName;

-

     @NotNull

     @ManyToOne(fetch = FetchType.LAZY)

     @JoinColumn(name = "dataspace_id")

@@ -77,13 +74,9 @@
 

     @OneToOne(fetch = FetchType.LAZY)

     @JoinColumn(name = "anchor_id")

-    private Fragment anchorFragment;

+    private AnchorEntity anchor;

 

     @OneToOne(fetch = FetchType.LAZY)

     @JoinColumn(name = "parent_id")

     private Fragment parentFragment;

-

-    @OneToOne(fetch = FetchType.LAZY)

-    @JoinColumn(name = "schema_set_id")

-    private SchemaSet schemaSet;

 }

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 d6579bd..fdb446c 100644
--- 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
@@ -24,14 +24,14 @@
 import java.util.Collection;
 import java.util.stream.Collectors;
 import org.onap.cps.spi.CpsAdminPersistenceService;
+import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.Dataspace;
-import org.onap.cps.spi.entities.Fragment;
 import org.onap.cps.spi.entities.SchemaSet;
 import org.onap.cps.spi.exceptions.AnchorAlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DataspaceAlreadyDefinedException;
 import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.repository.AnchorRepository;
 import org.onap.cps.spi.repository.DataspaceRepository;
-import org.onap.cps.spi.repository.FragmentRepository;
 import org.onap.cps.spi.repository.SchemaSetRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DataIntegrityViolationException;
@@ -44,7 +44,7 @@
     private DataspaceRepository dataspaceRepository;
 
     @Autowired
-    private FragmentRepository fragmentRepository;
+    private AnchorRepository anchorRepository;
 
     @Autowired
     private SchemaSetRepository schemaSetRepository;
@@ -62,14 +62,13 @@
     public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
         final Dataspace dataspace = dataspaceRepository.getByName(dataspaceName);
         final SchemaSet schemaSet = schemaSetRepository.getByDataspaceAndName(dataspace, schemaSetName);
-        final Fragment anchor = Fragment.builder()
-            .xpath(anchorName)
-            .anchorName(anchorName)
+        final AnchorEntity anchorEntity = AnchorEntity.builder()
+            .name(anchorName)
             .dataspace(dataspace)
             .schemaSet(schemaSet)
             .build();
         try {
-            fragmentRepository.save(anchor);
+            anchorRepository.save(anchorEntity);
         } catch (final DataIntegrityViolationException e) {
             throw new AnchorAlreadyDefinedException(dataspaceName, anchorName, e);
         }
@@ -78,13 +77,15 @@
     @Override
     public Collection<Anchor> getAnchors(final String dataspaceName) {
         final Dataspace dataspace = dataspaceRepository.getByName(dataspaceName);
-        final Collection<Fragment> fragments = fragmentRepository.findFragmentsThatAreAnchorsByDataspace(dataspace);
-        return fragments.stream().map(
-            entity -> Anchor.builder()
-                .name(entity.getAnchorName())
-                .dataspaceName(dataspaceName)
-                .schemaSetName(entity.getSchemaSet().getName())
-                .build()
-        ).collect(Collectors.toList());
+        final Collection<AnchorEntity> anchorEntities = anchorRepository.findAllByDataspace(dataspace);
+        return anchorEntities.stream().map(CpsAdminPersistenceServiceImpl::toAnchor).collect(Collectors.toList());
+    }
+
+    private static Anchor toAnchor(final AnchorEntity anchorEntity) {
+        return Anchor.builder()
+            .name(anchorEntity.getName())
+            .dataspaceName(anchorEntity.getDataspace().getName())
+            .schemaSetName(anchorEntity.getSchemaSet().getName())
+            .build();
     }
 }
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
new file mode 100644
index 0000000..df665f9
--- /dev/null
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
@@ -0,0 +1,33 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Pantheon.tech
+ *  ================================================================================
+ *  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.repository;
+
+import java.util.Collection;
+import java.util.Optional;
+import javax.validation.constraints.NotNull;
+import org.onap.cps.spi.entities.AnchorEntity;
+import org.onap.cps.spi.entities.Dataspace;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> {
+    Optional<AnchorEntity> findByDataspaceAndName(@NotNull Dataspace dataspace, @NotNull String name);
+
+    Collection<AnchorEntity> findAllByDataspace(@NotNull Dataspace dataspace);
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
index 2ee76cc..4521d09 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
@@ -21,22 +21,11 @@
 

 package org.onap.cps.spi.repository;

 

-import java.util.Collection;

-import java.util.Optional;

-import javax.validation.constraints.NotNull;

-import org.onap.cps.spi.entities.Dataspace;

 import org.onap.cps.spi.entities.Fragment;

 import org.springframework.data.jpa.repository.JpaRepository;

 import org.springframework.stereotype.Repository;

 

 @Repository

-public interface FragmentRepository extends JpaRepository<Fragment, Integer> {

+public interface FragmentRepository extends JpaRepository<Fragment, Long> {

 

-    Optional<Fragment> findByDataspaceAndAnchorName(@NotNull Dataspace dataspace, @NotNull String anchorName);

-

-    default Collection<Fragment> findFragmentsThatAreAnchorsByDataspace(Dataspace dataspace) {

-        return findFragmentsByDataspaceAndParentFragmentIsNull(dataspace);

-    }

-

-    Collection<Fragment> findFragmentsByDataspaceAndParentFragmentIsNull(Dataspace dataspace);

 }
\ No newline at end of file
diff --git a/cps-ri/src/main/resources/schema.sql b/cps-ri/src/main/resources/schema.sql
index 58b3c63..d37d932 100755
--- a/cps-ri/src/main/resources/schema.sql
+++ b/cps-ri/src/main/resources/schema.sql
@@ -53,18 +53,25 @@
     CONSTRAINT MODULE_DATASPACE FOREIGN KEY (DATASPACE_ID) REFERENCES DATASPACE (id) ON UPDATE CASCADE ON DELETE CASCADE

 );

 

+CREATE TABLE IF NOT EXISTS ANCHOR

+(

+    ID BIGSERIAL PRIMARY KEY,

+    NAME TEXT,

+    SCHEMA_SET_ID INTEGER REFERENCES SCHEMA_SET(ID),

+    DATASPACE_ID INTEGER NOT NULL REFERENCES DATASPACE(ID),

+    UNIQUE (DATASPACE_ID, NAME)

+);

+

 CREATE TABLE IF NOT EXISTS FRAGMENT

 (

     ID BIGSERIAL PRIMARY KEY,

     XPATH TEXT NOT NULL,

     ATTRIBUTES JSONB,

-    ANCHOR_NAME TEXT,

-    ANCHOR_ID BIGINT REFERENCES FRAGMENT(ID),

+    ANCHOR_ID BIGINT REFERENCES ANCHOR(ID),

     PARENT_ID BIGINT REFERENCES FRAGMENT(ID),

-    SCHEMA_SET_ID INTEGER REFERENCES SCHEMA_SET(ID),

     DATASPACE_ID INTEGER NOT NULL REFERENCES DATASPACE(ID),

     SCHEMA_NODE_ID INTEGER REFERENCES SCHEMA_NODE(ID),

-    UNIQUE (DATASPACE_ID, ANCHOR_NAME, XPATH)

+    UNIQUE (DATASPACE_ID, ANCHOR_ID, XPATH)

 );

 

 CREATE TABLE IF NOT EXISTS RELATION

@@ -78,9 +85,9 @@
 );

 

 CREATE INDEX  IF NOT EXISTS "FKI_FRAGMENT_DATASPACE_ID_FK"     ON FRAGMENT USING BTREE(DATASPACE_ID) ;

-CREATE INDEX  IF NOT EXISTS "FKI_FRAGMENT_SCHEMA_SET_ID_FK"    ON FRAGMENT USING BTREE(SCHEMA_SET_ID) ;

 CREATE INDEX  IF NOT EXISTS "FKI_FRAGMENT_PARENT_ID_FK"        ON FRAGMENT USING BTREE(PARENT_ID) ;

 CREATE INDEX  IF NOT EXISTS "FKI_FRAGMENT_ANCHOR_ID_FK"        ON FRAGMENT USING BTREE(ANCHOR_ID) ;

+CREATE INDEX  IF NOT EXISTS "FKI_ANCHOR_SCHEMA_SET_ID_FK"      ON ANCHOR USING BTREE(SCHEMA_SET_ID) ;

 CREATE INDEX  IF NOT EXISTS "PERF_SCHEMA_NODE_SCHEMA_NODE_ID"  ON SCHEMA_NODE USING BTREE(SCHEMA_NODE_IDENTIFIER) ;

 CREATE INDEX  IF NOT EXISTS "FKI_SCHEMA_NODE_ID_TO_ID"         ON FRAGMENT USING BTREE(SCHEMA_NODE_ID) ;

 CREATE INDEX  IF NOT EXISTS "FKI_RELATION_TYPE_ID_FK"          ON RELATION USING BTREE(RELATION_TYPE_ID);

diff --git a/cps-ri/src/test/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceTest.java b/cps-ri/src/test/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceTest.java
index 7497526..e5aac1e 100644
--- a/cps-ri/src/test/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceTest.java
+++ b/cps-ri/src/test/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceTest.java
@@ -29,15 +29,15 @@
 import org.junit.runner.RunWith;
 import org.onap.cps.DatabaseTestContainer;
 import org.onap.cps.spi.CpsAdminPersistenceService;
+import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.Dataspace;
-import org.onap.cps.spi.entities.Fragment;
 import org.onap.cps.spi.exceptions.AnchorAlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DataspaceAlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.repository.AnchorRepository;
 import org.onap.cps.spi.repository.DataspaceRepository;
-import org.onap.cps.spi.repository.FragmentRepository;
 import org.onap.cps.spi.repository.SchemaSetRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -69,7 +69,7 @@
     private CpsAdminPersistenceService cpsAdminPersistenceService;
 
     @Autowired
-    private FragmentRepository fragmentRepository;
+    private AnchorRepository anchorRepository;
 
     @Autowired
     private DataspaceRepository dataspaceRepository;
@@ -102,13 +102,13 @@
 
         // validate anchor persisted
         final Dataspace dataspace = dataspaceRepository.findByName(DATASPACE_NAME).orElseThrow();
-        final Fragment anchor =
-            fragmentRepository.findByDataspaceAndAnchorName(dataspace, ANCHOR_NAME_NEW).orElseThrow();
+        final AnchorEntity anchorEntity =
+            anchorRepository.findByDataspaceAndName(dataspace, ANCHOR_NAME_NEW).orElseThrow();
 
-        assertNotNull(anchor.getId());
-        assertEquals(ANCHOR_NAME_NEW, anchor.getAnchorName());
-        assertEquals(DATASPACE_NAME, anchor.getDataspace().getName());
-        assertEquals(SCHEMA_SET_NAME2, anchor.getSchemaSet().getName());
+        assertNotNull(anchorEntity.getId());
+        assertEquals(ANCHOR_NAME_NEW, anchorEntity.getName());
+        assertEquals(DATASPACE_NAME, anchorEntity.getDataspace().getName());
+        assertEquals(SCHEMA_SET_NAME2, anchorEntity.getSchemaSet().getName());
     }
 
     @Test(expected = DataspaceNotFoundException.class)
diff --git a/cps-ri/src/test/resources/data/anchor.sql b/cps-ri/src/test/resources/data/anchor.sql
index 5507559..1d9b4b1 100644
--- a/cps-ri/src/test/resources/data/anchor.sql
+++ b/cps-ri/src/test/resources/data/anchor.sql
@@ -4,6 +4,6 @@
 INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES
     (2001, 'SCHEMA-SET-001', 1001), (2002, 'SCHEMA-SET-002', 1001);
 
-INSERT INTO FRAGMENT (ID, XPATH, ANCHOR_NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
-    (3001, 'ANCHOR-001', 'ANCHOR-001', 1001, 2001),
-    (3002, 'ANCHOR-002', 'ANCHOR-002', 1001, 2002);
\ No newline at end of file
+INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
+    (3001, 'ANCHOR-001', 1001, 2001),
+    (3002, 'ANCHOR-002', 1001, 2002);
\ No newline at end of file
diff --git a/cps-ri/src/test/resources/data/clear-all.sql b/cps-ri/src/test/resources/data/clear-all.sql
index 522f9e6..9aee604 100644
--- a/cps-ri/src/test/resources/data/clear-all.sql
+++ b/cps-ri/src/test/resources/data/clear-all.sql
@@ -1,6 +1,7 @@
 DELETE FROM FRAGMENT;
--- clear all via dataspace table cleanup
--- all other data will be removed by cascade
+DELETE FROM ANCHOR;
 DELETE FROM DATASPACE;
--- explicit clear
+-- following tables are cleared by CASCADE constraint:
+-- SCHEMA_SET
+-- SCHEMA_SET_YANG_RESOURCES
 DELETE FROM YANG_RESOURCE;