CompositeStateBuilder added for building the compositeState

Added composite state to YangModelCmHandleRetriever

Issue-ID: CPS-878
Signed-off-by: puthuparambil.aditya <aditya.puthuparambil@bell.ca>
Change-Id: I8bdea55c0a8e27a906e24fc367dedf81c9b3501c
Signed-off-by: puthuparambil.aditya <aditya.puthuparambil@bell.ca>
diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml
index 573c76e..9467b8f 100644
--- a/cps-ncmp-service/pom.xml
+++ b/cps-ncmp-service/pom.xml
@@ -31,6 +31,9 @@
     </parent>
 
     <artifactId>cps-ncmp-service</artifactId>
+    <properties>
+    <minimum-coverage>0.93</minimum-coverage>
+    </properties>
 
     <dependencies>
         <dependency>
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java
index 5063e82..0edd68c 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Modifications Copyright (C) 2021 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -25,6 +26,8 @@
 import lombok.AllArgsConstructor;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.CompositeState;
+import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
@@ -53,7 +56,7 @@
         final DataNode cmHandleDataNode = getCmHandleDataNode(cmHandleId);
         final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
         ncmpServiceCmHandle.setCmHandleId(cmHandleId);
-        populateCmHandleProperties(cmHandleDataNode, ncmpServiceCmHandle);
+        populateCmHandleDetails(cmHandleDataNode, ncmpServiceCmHandle);
         return YangModelCmHandle.toYangModelCmHandle(
             (String) cmHandleDataNode.getLeaves().get("dmi-service-name"),
             (String) cmHandleDataNode.getLeaves().get("dmi-data-service-name"),
@@ -70,19 +73,24 @@
             FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
     }
 
-    private static void populateCmHandleProperties(final DataNode cmHandleDataNode,
+    private static void populateCmHandleDetails(final DataNode cmHandleDataNode,
                                                    final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final Map<String, String> dmiProperties = new LinkedHashMap<>();
         final Map<String, String> publicProperties = new LinkedHashMap<>();
+        final CompositeStateBuilder compositeStateBuilder = new CompositeStateBuilder();
+        CompositeState compositeState = compositeStateBuilder.build();
         for (final DataNode childDataNode: cmHandleDataNode.getChildDataNodes()) {
             if (childDataNode.getXpath().contains("/additional-properties[@name=")) {
                 addProperty(childDataNode, dmiProperties);
             } else if (childDataNode.getXpath().contains("/public-properties[@name=")) {
                 addProperty(childDataNode, publicProperties);
+            } else if (childDataNode.getXpath().endsWith("/state")) {
+                compositeState = compositeStateBuilder.fromDataNode(childDataNode).build();
             }
         }
         ncmpServiceCmHandle.setDmiProperties(dmiProperties);
         ncmpServiceCmHandle.setPublicProperties(publicProperties);
+        ncmpServiceCmHandle.setCompositeState(compositeState);
     }
 
     private static void addProperty(final DataNode propertyDataNode, final Map<String, String> propertiesAsMap) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java
new file mode 100644
index 0000000..d8f7080
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java
@@ -0,0 +1,147 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Bell Canada
+ * ================================================================================
+ * 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.ncmp.api.inventory;
+
+import org.onap.cps.ncmp.api.inventory.CompositeState.DataStores;
+import org.onap.cps.ncmp.api.inventory.CompositeState.LockReason;
+import org.onap.cps.ncmp.api.inventory.CompositeState.Operational;
+import org.onap.cps.ncmp.api.inventory.CompositeState.Running;
+import org.onap.cps.spi.model.DataNode;
+
+public class CompositeStateBuilder {
+
+    private CmHandleState cmHandleState;
+    private LockReason lockReason;
+    private DataStores datastores;
+    private String lastUpdatedTime;
+
+    /**
+     * To create the {@link CompositeState}.
+     *
+     * @return {@link DataNode}
+     */
+    public CompositeState build() {
+        final CompositeState compositeState = new CompositeState();
+        compositeState.setCmhandleState(cmHandleState);
+        compositeState.setLockReason(lockReason);
+        compositeState.setDataStores(datastores);
+        compositeState.setLastUpdateTime(lastUpdatedTime);
+        return compositeState;
+    }
+
+    /**
+     * To use attributes for creating {@link CompositeState}.
+     *
+     * @param cmHandleState for the data node
+     * @return CompositeStateBuilder
+     */
+    public CompositeStateBuilder withCmHandleState(final CmHandleState cmHandleState) {
+        this.cmHandleState = cmHandleState;
+        return this;
+    }
+
+    /**
+     * To use attributes for creating {@link CompositeState}.
+     *
+     * @param reason for the locked state
+     * @param details for the locked state
+     * @return CompositeStateBuilder
+     */
+    public CompositeStateBuilder withLockReason(final String reason, final String details) {
+        this.lockReason = LockReason.builder().reason(reason).details(details).build();
+        return this;
+    }
+
+    /**
+     * To use attributes for creating {@link CompositeState}.
+     *
+     * @param time for the state change
+     * @return CompositeStateBuilder
+     */
+    public CompositeStateBuilder withLastUpdatedTime(final String time) {
+        this.lastUpdatedTime = time;
+        return this;
+    }
+
+    /**
+     * To use attributes for creating {@link CompositeState}.
+     *
+     * @param syncState for the locked state
+     * @param lastSyncTime for the locked state
+     * @return CompositeStateBuilder
+     */
+    public CompositeStateBuilder withOperationalDataStores(final String syncState, final String lastSyncTime) {
+        this.datastores = DataStores.builder().operationalDataStore(
+            Operational.builder().syncState(syncState).lastSyncTime(lastSyncTime).build()).build();
+        return this;
+    }
+
+    /**
+     * To use attributes for creating {@link CompositeState}.
+     *
+     * @param syncState for the locked state
+     * @param lastSyncTime for the locked state
+     * @return CompositeStateBuilder
+     */
+    public CompositeStateBuilder withRunningDataStores(final String syncState, final String lastSyncTime) {
+        this.datastores = DataStores.builder().runningDataStore(
+            Running.builder().syncState(syncState).lastSyncTime(lastSyncTime).build()).build();
+        return this;
+    }
+
+    /**
+     * To use dataNode for creating {@link CompositeState}.
+     *
+     * @param dataNode for the dataNode
+     * @return CompositeState
+     */
+    public CompositeStateBuilder fromDataNode(final DataNode dataNode) {
+        this.cmHandleState = CmHandleState.valueOf((String) dataNode.getLeaves()
+            .get("cm-handle-state"));
+        for (final DataNode stateChildNode : dataNode.getChildDataNodes()) {
+            if (stateChildNode.getXpath().endsWith("/lock-reason")) {
+                this.lockReason = new LockReason((String) stateChildNode.getLeaves().get("reason"),
+                    (String) stateChildNode.getLeaves().get("details"));
+            }
+            if (stateChildNode.getXpath().endsWith("/datastores")) {
+                for (final DataNode dataStoreNodes : stateChildNode.getChildDataNodes()) {
+                    Operational operationalDataStore = null;
+                    Running runningDataStore = null;
+                    if (dataStoreNodes.getXpath().contains("/operational")) {
+                        operationalDataStore = Operational.builder()
+                            .syncState((String) dataStoreNodes.getLeaves().get("sync-state"))
+                            .lastSyncTime((String) dataStoreNodes.getLeaves().get("last-sync-time"))
+                            .build();
+                    } else {
+                        runningDataStore = Running.builder()
+                            .syncState((String) dataStoreNodes.getLeaves().get("sync-state"))
+                            .lastSyncTime((String) dataStoreNodes.getLeaves().get("last-sync-time"))
+                            .build();
+                    }
+                    this.datastores = DataStores.builder().operationalDataStore(operationalDataStore)
+                        .runningDataStore(runningDataStore).build();
+                }
+            }
+        }
+        return this;
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy
index 5ecc8b0..7fbfa77 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy
@@ -22,6 +22,8 @@
 
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.inventory.CmHandleState
+import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder
 import org.onap.cps.spi.exceptions.DataValidationException
 import spock.lang.Shared
 
@@ -40,6 +42,9 @@
     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
 
     @Shared
+    def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).build()
+
+    @Shared
     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]),
                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
 
@@ -49,6 +54,9 @@
     @Shared
     def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
 
+    @Shared
+    def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])]
+
     def "Retrieve CmHandle using datanode with #scenario."() {
         given: 'the cps data service returns a data node from the DMI registry'
             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
@@ -63,12 +71,15 @@
         and: 'the expected DMI properties'
             result.dmiProperties == expectedDmiProperties
             result.publicProperties == expectedPublicProperties
+        and: 'the state details are returned'
+            result.compositeState.cmhandleState == expectedCompositeState
         where: 'the following parameters are used'
-            scenario                    | childDataNodes                                || expectedDmiProperties                               || expectedPublicProperties
-            'no properties'             | []                                            || []                                                  || []
-            'DMI and public properties' | childDataNodesForCmHandleWithAllProperties    || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")]
-            'just DMI properties'       | childDataNodesForCmHandleWithDMIProperties    || [new YangModelCmHandle.Property("name1", "value1")] || []
-            'just public properties'    | childDataNodesForCmHandleWithPublicProperties || []                                                  || [new YangModelCmHandle.Property("name2", "value2")]
+            scenario                    | childDataNodes                                || expectedDmiProperties                               || expectedPublicProperties                              || expectedCompositeState
+            'no properties'             | []                                            || []                                                  || []                                                    || null
+            'DMI and public properties' | childDataNodesForCmHandleWithAllProperties    || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")]   || null
+            'just DMI properties'       | childDataNodesForCmHandleWithDMIProperties    || [new YangModelCmHandle.Property("name1", "value1")] || []                                                    || null
+            'just public properties'    | childDataNodesForCmHandleWithPublicProperties || []                                                  || [new YangModelCmHandle.Property("name2", "value2")]   || null
+            'with state details'        | childDataNodesForCmHandleWithState            || []                                                  || []                                                    || CmHandleState.ADVISED
     }
 
     def "Retrieve CmHandle using datanode with invalid CmHandle id."() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy
new file mode 100644
index 0000000..2be5239
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy
@@ -0,0 +1,65 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Bell Canada
+ * ================================================================================
+ * 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.ncmp.api.inventory
+
+import org.onap.cps.spi.model.DataNode
+import org.onap.cps.spi.model.DataNodeBuilder
+import spock.lang.Specification
+
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
+
+class CompositeStateBuilderSpec extends Specification {
+
+    def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
+        .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
+
+    def static cmHandleId = 'myHandle1'
+    def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}/state']"
+    def static stateDataNodes = [new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/lock-reason")
+                                         .withLeaves(['reason': 'lock reason', 'details': 'lock details']).build(),
+                                 new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/datastores")
+                                            .withChildDataNodes(Arrays.asList(new DataNodeBuilder()
+                                                    .withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/datastores/operational")
+                                                    .withLeaves(['sync-state': 'UNSYNCHRONIZED']).build())).build()]
+    def static cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: stateDataNodes, leaves: ['cm-handle-state': 'ADVISED'])
+
+    def "Composite State Specification"() {
+        when: 'using composite state builder '
+            def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED)
+                    .withLockReason("lock-reason","").withOperationalDataStores("UNSYNCHRONIZED",
+                    formattedDateAndTime.toString()).withLastUpdatedTime(formattedDateAndTime).build();
+        then: 'it matches expected cm handle state and data store sync state'
+            assert compositeState.getCmhandleState() == CmHandleState.ADVISED
+            assert compositeState.dataStores.operationalDataStore.syncState == 'UNSYNCHRONIZED'
+    }
+
+    def "Build composite state from DataNode "() {
+        given: "a Data Node "
+            def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
+        when: 'build from data node function is invoked'
+            def compositeState = new CompositeStateBuilder().fromDataNode(cmHandleDataNode).build()
+        then: 'it matches expected state model as JSON'
+            assert compositeState.cmhandleState == CmHandleState.ADVISED
+    }
+
+}