From 6bd247357a3112be96b94d52532aa09231ed520c Mon Sep 17 00:00:00 2001 From: niamhcore Date: Mon, 11 Jan 2021 17:14:08 +0000 Subject: [PATCH] Persisting data nodes (fragments tree structure) Issue-ID: CPS-136 Signed-off-by: niamhcore Change-Id: I559afad41bf0eab1cc98c777a418b348c9c1b81c --- .../org/onap/cps/spi/entities/FragmentEntity.java | 11 +- .../spi/impl/CpsDataPersistenceServiceImpl.java | 88 ++++++++++++ .../spi/impl/CpsDataPersistenceServiceTest.java | 159 +++++++++++++++++++++ cps-ri/src/test/resources/data/fragment.sql | 13 ++ .../onap/cps/spi/CpsDataPersistenceService.java | 12 ++ .../main/java/org/onap/cps/spi/model/DataNode.java | 4 +- 6 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java create mode 100644 cps-ri/src/test/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceTest.java create mode 100644 cps-ri/src/test/resources/data/fragment.sql diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java index d1557489c..677652899 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java @@ -22,6 +22,8 @@ package org.onap.cps.spi.entities; import com.vladmihalcea.hibernate.type.json.JsonBinaryType; import java.io.Serializable; +import java.util.Set; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -30,10 +32,13 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.OneToOne; +import javax.persistence.Table; import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -44,12 +49,14 @@ import org.hibernate.annotations.TypeDefs; /** * Entity to store a fragment. */ +@Data @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder @Entity +@Table(name = "fragment") @TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)}) public class FragmentEntity implements Serializable { @@ -76,7 +83,7 @@ public class FragmentEntity implements Serializable { @JoinColumn(name = "anchor_id") private AnchorEntity anchor; - @OneToOne(fetch = FetchType.LAZY) + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") - private FragmentEntity parentFragment; + private Set childFragments; } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java new file mode 100644 index 000000000..283d64682 --- /dev/null +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -0,0 +1,88 @@ +/* + * ============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.impl; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.onap.cps.spi.CpsDataPersistenceService; +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.model.DataNode; +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.stereotype.Service; + +@Service +public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService { + + @Autowired + private DataspaceRepository dataspaceRepository; + + @Autowired + private AnchorRepository anchorRepository; + + @Autowired + private FragmentRepository fragmentRepository; + + private static Gson GSON = new GsonBuilder().create(); + + @Override + public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, + dataNode); + fragmentRepository.save(fragmentEntity); + } + + /** + * Convert DataNode object into Fragment and places the result in the fragments placeholder. Performs same action + * for all DataNode children recursively. + * + * @param dataspaceEntity dataspace + * @param anchorEntity anchorEntity + * @param dataNodeToBeConverted dataNode + * @return a Fragment built from current DataNode + */ + private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, + final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { + final FragmentEntity parentFragment = FragmentEntity.builder() + .dataspace(dataspaceEntity) + .anchor(anchorEntity) + .xpath(dataNodeToBeConverted.getXpath()) + .attributes(GSON.toJson(dataNodeToBeConverted.getLeaves())) + .build(); + + final Builder fragmentEntityBuilder = ImmutableSet.builder(); + for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { + final FragmentEntity childFragment = + convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(), + childDataNode); + fragmentEntityBuilder.add(childFragment); + } + parentFragment.setChildFragments(fragmentEntityBuilder.build()); + return parentFragment; + } +} diff --git a/cps-ri/src/test/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceTest.java b/cps-ri/src/test/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceTest.java new file mode 100644 index 000000000..db2941c19 --- /dev/null +++ b/cps-ri/src/test/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceTest.java @@ -0,0 +1,159 @@ +/* + * ============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.impl; + +import static junit.framework.TestCase.assertEquals; + +import com.google.common.collect.ImmutableSet; +import java.util.Collections; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.onap.cps.DatabaseTestContainer; +import org.onap.cps.spi.CpsDataPersistenceService; +import org.onap.cps.spi.entities.FragmentEntity; +import org.onap.cps.spi.exceptions.AnchorNotFoundException; +import org.onap.cps.spi.exceptions.DataspaceNotFoundException; +import org.onap.cps.spi.model.DataNode; +import org.onap.cps.spi.repository.FragmentRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit4.SpringRunner; + + +@RunWith(SpringRunner.class) +@SpringBootTest +public class CpsDataPersistenceServiceTest { + + private static final String CLEAR_DATA = "/data/clear-all.sql"; + private static final String SET_DATA = "/data/fragment.sql"; + + private static final String NON_EXISTING_DATASPACE_NAME = "NON EXISTING DATASPACE"; + private static final String DATASPACE_NAME = "DATASPACE-001"; + private static final String ANCHOR_NAME1 = "ANCHOR-001"; + private static final String NON_EXISTING_ANCHOR_NAME = "NON EXISTING ANCHOR"; + private static final String PARENT_XPATH = "/parent"; + private static final String CHILD_XPATH = "/parent/child"; + private static final String GRAND_CHILD_XPATH = "/parent/child/grandchild"; + private static final String PARENT_XPATH_NEW = "/parent-new"; + private static final String CHILD_XPATH_NEW = "/parent/child-new"; + private static final String GRAND_CHILD_XPATH_NEW = "/parent/child/grandchild-new"; + private static final long PARENT_ID = 3001; + private static final long CHILD_ID = 3002; + private static final long GRAND_CHILD_ID = 3003; + private static final long PARENT_ID_NEW = 2; + private static final long CHILD_ID_NEW = 3; + private static final long GRAND_CHILD_ID_NEW = 4; + + @ClassRule + public static DatabaseTestContainer databaseTestContainer = DatabaseTestContainer.getInstance(); + + @Autowired + private CpsDataPersistenceService cpsDataPersistenceService; + + @Autowired + private FragmentRepository fragmentRepository; + + @Test + @Sql({CLEAR_DATA, SET_DATA}) + public void testGetFragmentsWithChildAndGrandChild() { + final FragmentEntity parentFragment = fragmentRepository.findById(PARENT_ID).orElseThrow(); + final FragmentEntity childFragment = fragmentRepository.findById(CHILD_ID).orElseThrow(); + final FragmentEntity grandChildFragment = fragmentRepository.findById(GRAND_CHILD_ID).orElseThrow(); + + assertFragment(parentFragment, childFragment, grandChildFragment, PARENT_XPATH, CHILD_XPATH, GRAND_CHILD_XPATH); + } + + @Test(expected = DataspaceNotFoundException.class) + @Sql({CLEAR_DATA, SET_DATA}) + public void testStoreDataNodeAtNonExistingDataspace() { + cpsDataPersistenceService + .storeDataNode(NON_EXISTING_DATASPACE_NAME, ANCHOR_NAME1, + createDataNodeWithChildAndGrandChild(PARENT_XPATH_NEW, CHILD_XPATH_NEW, GRAND_CHILD_XPATH_NEW)); + } + + @Test(expected = AnchorNotFoundException.class) + @Sql({CLEAR_DATA, SET_DATA}) + public void testStoreDataNodeAtNonExistingAnchor() { + cpsDataPersistenceService + .storeDataNode(DATASPACE_NAME, NON_EXISTING_ANCHOR_NAME, + createDataNodeWithChildAndGrandChild(PARENT_XPATH_NEW, CHILD_XPATH_NEW, GRAND_CHILD_XPATH_NEW)); + } + + @Test(expected = DataIntegrityViolationException.class) + @Sql({CLEAR_DATA, SET_DATA}) + public void testStoreDataNodeWithIntegrityException() { + cpsDataPersistenceService.storeDataNode(DATASPACE_NAME, ANCHOR_NAME1, + createDataNodeWithChildAndGrandChild(PARENT_XPATH, CHILD_XPATH, GRAND_CHILD_XPATH)); + } + + @Test + @Sql({CLEAR_DATA, SET_DATA}) + public void testStoreDataNodeWithChildrenAndGrandChildren() { + cpsDataPersistenceService.storeDataNode(DATASPACE_NAME, ANCHOR_NAME1, + createDataNodeWithChildAndGrandChild(PARENT_XPATH_NEW, CHILD_XPATH_NEW, GRAND_CHILD_XPATH_NEW)); + + final FragmentEntity parentFragment = fragmentRepository.findById(PARENT_ID_NEW).orElseThrow(); + final FragmentEntity childFragment = fragmentRepository.findById(CHILD_ID_NEW).orElseThrow(); + final FragmentEntity grandChildFragment = fragmentRepository.findById(GRAND_CHILD_ID_NEW).orElseThrow(); + + assertFragment(parentFragment, childFragment, grandChildFragment, PARENT_XPATH_NEW, CHILD_XPATH_NEW, + GRAND_CHILD_XPATH_NEW); + } + + private void assertFragment(final FragmentEntity parentFragment, final FragmentEntity childFragment, + final FragmentEntity grandChildFragment, final String parentXpath, final String childXpath, + final String grandChildXpath) { + assertEquals(parentXpath, parentFragment.getXpath()); + assertEquals(DATASPACE_NAME, parentFragment.getDataspace().getName()); + assertEquals(ANCHOR_NAME1, parentFragment.getAnchor().getName()); + + assertEquals(childXpath, childFragment.getXpath()); + assertEquals(DATASPACE_NAME, childFragment.getDataspace().getName()); + assertEquals(ANCHOR_NAME1, childFragment.getAnchor().getName()); + + assertEquals(grandChildXpath, grandChildFragment.getXpath()); + assertEquals(DATASPACE_NAME, grandChildFragment.getDataspace().getName()); + assertEquals(ANCHOR_NAME1, grandChildFragment.getAnchor().getName()); + } + + private DataNode createDataNodeWithChildAndGrandChild(final String parentXpath, final String childXpath, + final String grandChildXpath) { + final DataNode parentDataNode = DataNode.builder() + .xpath(parentXpath) + .build(); + + final DataNode childDataNode = DataNode.builder() + .xpath(childXpath) + .childDataNodes(Collections.emptySet()) + .build(); + + final DataNode grandChildDataNode = DataNode.builder() + .xpath(grandChildXpath) + .childDataNodes(Collections.emptySet()) + .build(); + + parentDataNode.setChildDataNodes(ImmutableSet.of(childDataNode)); + childDataNode.setChildDataNodes(ImmutableSet.of(grandChildDataNode)); + return parentDataNode; + } +} diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql new file mode 100644 index 000000000..c50f59530 --- /dev/null +++ b/cps-ri/src/test/resources/data/fragment.sql @@ -0,0 +1,13 @@ +INSERT INTO DATASPACE (ID, NAME) VALUES + (1001, 'DATASPACE-001'); + +INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES + (2001, 'SCHEMA-SET-001', 1001); + +INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES + (3001, 'ANCHOR-001', 1001, 2001); + +INSERT INTO FRAGMENT (ID, XPATH, ANCHOR_ID, PARENT_ID, DATASPACE_ID) VALUES + (3001, '/parent', 3001, null, 1001), + (3002, '/parent/child', 3001, 3001, 1001), + (3003, '/parent/child/grandchild', 3001, 3002, 1001); \ No newline at end of file diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java index 12037066d..50ece0e27 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java @@ -20,6 +20,9 @@ package org.onap.cps.spi; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.onap.cps.spi.model.DataNode; + /* Data Store interface that is responsible for handling yang data. Please follow guidelines in https://gerrit.nordix.org/#/c/onap/ccsdk/features/+/6698/19/cps/interface-proposal/src/main/java/cps/javadoc/spi/DataStoreService.java @@ -27,4 +30,13 @@ package org.onap.cps.spi; */ public interface CpsDataPersistenceService { + /** + * Store a datanode. + * + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param dataNode data node + */ + void storeDataNode(@NonNull String dataspaceName, @NonNull String anchorName, + @NonNull DataNode dataNode); } diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java index f9eb22493..e9c6b56ea 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java @@ -34,9 +34,11 @@ import lombok.NoArgsConstructor; public class DataNode { private String dataspace; - private String moduleSetName; + private String schemaSetName; + private String anchorName; private ModuleReference moduleReference; private String xpath; private Map leaves; private Collection xpathsChildren; + private Collection childDataNodes; } -- 2.16.6