Composite State to handle dmi-reg YANG updates

- Introduce CompositeState object which handles change in updated YANG for dmi-registry
- Used Builder pattern as some of the fields are optional
- Removed the abstract ready method from CmHandleState which was used as
  state machine
- Fixed few test cases

Issue-ID: CPS-1042
Change-Id: I8aaf6f819c66b3a9d30c5e8f0a0007f9528b247f
Signed-off-by: mpriyank <priyank.maheshwari@est.tech>
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
index 5680d54..d4c64ea 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
@@ -34,7 +34,7 @@
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService;
-import org.onap.cps.ncmp.api.inventory.CmHandleState;
+import org.onap.cps.ncmp.api.inventory.CompositeState;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.utils.CpsValidator;
 
@@ -57,7 +57,7 @@
     private String dmiDataServiceName;
 
     @JsonProperty("state")
-    private CmHandleState cmHandleState;
+    private CompositeState compositeState;
 
     @JsonProperty("dmi-model-service-name")
     private String dmiModelServiceName;
@@ -70,8 +70,9 @@
 
     /**
      * Create a yangModelCmHandle.
-     * @param dmiServiceName dmi service name
-     * @param dmiDataServiceName dmi data service name
+     *
+     * @param dmiServiceName      dmi service name
+     * @param dmiDataServiceName  dmi data service name
      * @param dmiModelServiceName dmi model service name
      * @param ncmpServiceCmHandle the cm handle
      * @return instance of yangModelCmHandle
@@ -88,12 +89,13 @@
         yangModelCmHandle.setDmiModelServiceName(dmiModelServiceName);
         yangModelCmHandle.setDmiProperties(asYangModelCmHandleProperties(ncmpServiceCmHandle.getDmiProperties()));
         yangModelCmHandle.setPublicProperties(asYangModelCmHandleProperties(
-            ncmpServiceCmHandle.getPublicProperties()));
+                ncmpServiceCmHandle.getPublicProperties()));
         return yangModelCmHandle;
     }
 
     /**
      * Resolve a dmi service name.
+     *
      * @param requiredService indicates what typo of service is required
      * @return dmi service name
      */
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleState.java
index 0fce1d1..24fab32 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleState.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleState.java
@@ -21,21 +21,5 @@
 package org.onap.cps.ncmp.api.inventory;
 
 public enum CmHandleState {
-
-    ADVISED {
-        @Override
-        public CmHandleState ready() {
-            return READY;
-        }
-    },
-    READY {
-        @Override
-        public CmHandleState ready() {
-            return this;
-        }
-
-    };
-
-    public abstract CmHandleState ready();
-
+    ADVISED, READY;
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java
new file mode 100644
index 0000000..9ac49a6
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java
@@ -0,0 +1,104 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 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.ncmp.api.inventory;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * State Model to store state corresponding to the Yang resource dmi-registry model.
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class CompositeState {
+
+    @JsonProperty("cm-handle-state")
+    private CmHandleState cmhandleState;
+
+    @JsonProperty("lock-reason")
+    private LockReason lockReason;
+
+    @JsonProperty("last-update-time")
+    private String lastUpdateTime;
+
+    @JsonProperty("data-sync-enabled")
+    private Boolean dataSyncEnabled;
+
+    @JsonProperty("datastores")
+    private DataStores dataStores;
+
+    @Data
+    @Builder
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    public static class LockReason {
+
+        @JsonProperty("reason")
+        private String reason;
+
+        @JsonProperty("details")
+        private String details;
+
+    }
+
+    @Data
+    @Builder
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    public static class DataStores {
+
+        @JsonProperty("operational")
+        private Operational operationalDataStore;
+
+        @JsonProperty("running")
+        private Running runningDataStore;
+    }
+
+    @Data
+    @Builder
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    public static class Operational {
+
+        @JsonProperty("sync-state")
+        private String syncState;
+
+        @JsonProperty("last-sync-time")
+        private String lastSyncTime;
+    }
+
+    @Data
+    @Builder
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    public static class Running {
+
+        @JsonProperty("sync-state")
+        private String syncState;
+
+        @JsonProperty("last-sync-time")
+        private String lastSyncTime;
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java
index 368262b..7299e2a 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java
@@ -43,11 +43,11 @@
     public void executeAdvisedCmHandlePoll() {
         YangModelCmHandle advisedCmHandle = syncUtils.getAnAdvisedCmHandle();
         while (advisedCmHandle != null) {
-            final CmHandleState cmHandleState = advisedCmHandle.getCmHandleState();
             moduleSyncService.syncAndCreateSchemaSet(advisedCmHandle);
             // ToDo Lock Cm Handle if module sync fails
-            syncUtils.updateCmHandleState(advisedCmHandle, cmHandleState.ready());
-            log.info("{} is now in {} state", advisedCmHandle.getId(), advisedCmHandle.getCmHandleState());
+            syncUtils.updateCmHandleState(advisedCmHandle, CmHandleState.READY);
+            log.info("{} is now in {} state", advisedCmHandle.getId(),
+                    advisedCmHandle.getCompositeState().getCmhandleState());
             advisedCmHandle = syncUtils.getAnAdvisedCmHandle();
         }
         log.debug("No Cm-Handles currently found in an ADVISED state");
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java
index 019ab08..3bc43c5 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java
@@ -78,7 +78,7 @@
      * @param cmHandleState cm handle state
      */
     public void updateCmHandleState(final YangModelCmHandle yangModelCmHandle, final CmHandleState cmHandleState) {
-        yangModelCmHandle.setCmHandleState(cmHandleState);
+        yangModelCmHandle.getCompositeState().setCmhandleState(cmHandleState);
         final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}",
             jsonObjectMapper.asJsonString(yangModelCmHandle));
         cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy
new file mode 100644
index 0000000..4f4cccf
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy
@@ -0,0 +1,64 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 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.ncmp.api.inventory
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import spock.lang.Specification
+
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
+
+import static CompositeState.DataStores
+import static CompositeState.LockReason
+import static CompositeState.Operational
+import static CompositeState.Running
+import static org.onap.cps.ncmp.utils.TestUtils.getResourceFileContent
+import static org.springframework.util.StringUtils.trimAllWhitespace
+
+class CompositeStateSpec extends Specification {
+
+    def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 1, 1, 1, 1, 1, 1, ZoneOffset.MIN))
+    def objectMapper = new ObjectMapper()
+
+    def "Composite State Specification"() {
+        given: "a Composite State"
+            def compositeState = new CompositeState(cmhandleState: CmHandleState.ADVISED,
+                lockReason: LockReason.builder().reason('lock-reason').details("lock-misbehaving-details").build(),
+                lastUpdateTime: formattedDateAndTime.toString(),
+                dataSyncEnabled: false,
+                dataStores: dataStores())
+        when: 'it is represented as JSON'
+            def jsonStateModelAsString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(compositeState)
+        then: 'it matches expected state model as JSON'
+            def expectedStateModelAsjson = getResourceFileContent('expectedStateModel.json')
+            assert trimAllWhitespace(expectedStateModelAsjson) == trimAllWhitespace(jsonStateModelAsString)
+    }
+
+    def dataStores() {
+        DataStores.builder().operationalDataStore(Operational.builder()
+            .syncState('NONE_REQUESTED')
+            .lastSyncTime(formattedDateAndTime.toString()).build()).runningDataStore(Running.builder()
+            .syncState('NONE_REQUESTED')
+            .lastSyncTime(formattedDateAndTime.toString()).build())
+            .build()
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/CmHandleStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/CmHandleStateSpec.groovy
index 923a903..bfc5c6f 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/CmHandleStateSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/CmHandleStateSpec.groovy
@@ -29,7 +29,7 @@
         given: 'a cm handle with an ADVISED state'
             def cmHandleState = CmHandleState.ADVISED
         when: 'the state transitions to the READY state'
-            cmHandleState = cmHandleState.ready()
+            cmHandleState = CmHandleState.READY
         then: 'the cm handle state changes to READY'
             assert CmHandleState.READY == cmHandleState
     }
@@ -38,7 +38,7 @@
         given: 'a cm handle with a READY state'
             def cmHandleState = CmHandleState.READY
         when: 'the state transitions to READY state'
-            cmHandleState = cmHandleState.ready()
+            cmHandleState = CmHandleState.READY
         then: 'the cm handle state remains as READY'
             assert CmHandleState.READY == cmHandleState
     }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy
index 0a06fba..35de99f 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy
@@ -23,6 +23,7 @@
 
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.CmHandleState
+import org.onap.cps.ncmp.api.inventory.CompositeState
 import spock.lang.Specification
 
 class ModuleSyncSpec extends Specification {
@@ -37,8 +38,10 @@
 
     def 'Schedule a Cm-Handle Sync for ADVISED Cm-Handles'() {
         given: 'cm handles in an advised state'
-            def yangModelCmHandle1 = new YangModelCmHandle(cmHandleState: cmHandleState)
-            def yangModelCmHandle2 = new YangModelCmHandle(cmHandleState: cmHandleState)
+            def compositeState = new CompositeState()
+            compositeState.cmhandleState = cmHandleState
+            def yangModelCmHandle1 = new YangModelCmHandle(compositeState: compositeState)
+            def yangModelCmHandle2 = new YangModelCmHandle(compositeState: compositeState)
         and: 'sync utilities return a cm handle twice'
             mockSyncUtils.getAnAdvisedCmHandle() >>> [yangModelCmHandle1, yangModelCmHandle2, null]
         when: 'module sync poll is executed'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy
index e5d3652..c80263e 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy
@@ -25,6 +25,7 @@
 import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.CmHandleState
+import org.onap.cps.ncmp.api.inventory.CompositeState
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
@@ -68,8 +69,10 @@
 
     def 'Update cm handle state from Advised to Ready'() {
         given: 'a yang model cm handle and the expected json data'
-            def yangModelCmHandle = new YangModelCmHandle(id: 'Some-Cm-Handle', cmHandleState: CmHandleState.ADVISED)
-            def expectedJsonData = '{"cm-handles":[{"id":"Some-Cm-Handle","state":"READY"}]}'
+            def compositeState = new CompositeState()
+            compositeState.cmhandleState = CmHandleState.ADVISED
+            def yangModelCmHandle = new YangModelCmHandle(id: 'Some-Cm-Handle', compositeState: compositeState )
+            def expectedJsonData = '{"cm-handles":[{"id":"Some-Cm-Handle","state":{"cm-handle-state":"READY"}}]}'
         when: 'update cm handle state is called'
             objectUnderTest.updateCmHandleState(yangModelCmHandle, CmHandleState.READY)
         then: 'update data note leaves is invoked with the correct params'
diff --git a/cps-ncmp-service/src/test/resources/expectedStateModel.json b/cps-ncmp-service/src/test/resources/expectedStateModel.json
new file mode 100644
index 0000000..a416194
--- /dev/null
+++ b/cps-ncmp-service/src/test/resources/expectedStateModel.json
@@ -0,0 +1,19 @@
+{
+  "cm-handle-state" : "ADVISED",
+  "lock-reason" : {
+    "reason" : "lock-reason",
+    "details" : "lock-misbehaving-details"
+  },
+  "last-update-time" : "2022-01-01T01:01:01.000-1800",
+  "data-sync-enabled" : false,
+  "datastores" : {
+    "operational" : {
+      "sync-state" : "NONE_REQUESTED",
+      "last-sync-time" : "2022-01-01T01:01:01.000-1800"
+    },
+    "running" : {
+      "sync-state" : "NONE_REQUESTED",
+      "last-sync-time" : "2022-01-01T01:01:01.000-1800"
+    }
+  }
+}
\ No newline at end of file