Retry mechanism (with back off algorithm) is removed with more frequent watchdog... 20/138920/8 master
authorsourabh_sourabh <sourabh.sourabh@est.tech>
Wed, 11 Sep 2024 15:51:01 +0000 (16:51 +0100)
committersourabh_sourabh <sourabh.sourabh@est.tech>
Thu, 19 Sep 2024 14:13:55 +0000 (15:13 +0100)
- Increased watchdog frequency for locked cm handle.
- Removed retry backoff algorithm for locked cm handle.

Issue-ID: CPS-2395
Change-Id: I54d0ec8f9de53a7d181639c14aaaa93736f03e19
Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
545 files changed:
checkstyle/pom.xml
cps-application/pom.xml
cps-application/src/main/resources/application.yml
cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java
cps-bom/pom.xml
cps-dependencies/pom.xml
cps-events/pom.xml
cps-ncmp-events/pom.xml
cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/dmi-in-event-schema-1.0.0.json [moved from cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/cm-notification-subscription-dmi-in-event-schema-1.0.0.json with 86% similarity]
cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/dmi-out-event-schema-1.0.0.json [moved from cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/cm-notification-subscription-dmi-out-event-schema-1.0.0.json with 78% similarity]
cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/ncmp-in-event-schema-1.0.0.json [moved from cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/cm-notification-subscription-ncmp-in-event-schema-1.0.0.json with 85% similarity]
cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/ncmp-out-event-schema-1.0.0.json [new file with mode: 0644]
cps-ncmp-events/src/main/resources/schemas/dmidataavc/avc-event-schema-1.0.0.json
cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml
cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml
cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java
cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/test/groovy/org/onap/cps/ncmp/rest/stub/SampleCpsNcmpClientSpec.groovy
cps-ncmp-rest-stub/pom.xml
cps-ncmp-rest/docs/openapi/components.yaml
cps-ncmp-rest/docs/openapi/ncmp.yml
cps-ncmp-rest/docs/openapi/openapi-inventory.yml
cps-ncmp-rest/docs/openapi/openapi.yml
cps-ncmp-rest/pom.xml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java with 75% similarity, mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutor.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleStateMapper.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/CmHandleStateMapper.java with 94% similarity]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DataOperationRequestMapper.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/DataOperationRequestMapper.java with 91% similarity]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/NcmpRestInputMapper.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapper.java with 93% similarity]
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy [moved from cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy with 60% similarity]
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutorSpec.groovy [deleted file]
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleStateMapperSpec.groovy [moved from cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/CmHandleStateMapperSpec.groovy with 89% similarity]
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/DeprecationHelperSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/NcmpRestInputMapperSpec.groovy [moved from cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapperSpec.groovy with 97% similarity]
cps-ncmp-rest/src/test/resources/application.yml
cps-ncmp-service/pom.xml
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NcmpResponseStatus.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyQueryService.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/exceptions/InvalidDatastoreException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidDatastoreException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/exceptions/InvalidOperationException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidOperationException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/exceptions/OperationNotSupportedException.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/OperationNotSupportedException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/CmResourceAddress.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmResourceAddress.java with 59% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/DataOperationDefinition.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DataOperationDefinition.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/DataOperationRequest.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DataOperationRequest.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/DatastoreType.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DatastoreType.java with 94% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/data/models/OperationType.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java with 72% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobResultService.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/DataJobService.java with 54% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobStatusService.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobMetadata.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobMetadata.java with 94% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobReadRequest.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobReadRequest.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DataJobWriteRequest.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/DataJobWriteRequest.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DmiWriteOperation.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ProducerKey.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ReadOperation.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/ReadOperation.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteRequest.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteResponse.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/WriteOperation.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/datajob/WriteOperation.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/DmiClientRequestException.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/DmiRequestException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiRequestException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/InvalidTopicException.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/NcmpException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NcmpException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PayloadTooLargeException.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/PayloadTooLargeException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/HttpClientRequestException.java with 61% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/ServerNcmpException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/ServerNcmpException.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/DataJobServiceImpl.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDelta.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandler.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandler.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventPublishingTask.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapper.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DataNodeHelper.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmDataSubscriptionEvent.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleQueryApiParameters.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleQueryServiceParameters.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryServiceParameters.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponse.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CompositeState.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeState.java with 94% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilder.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilder.java with 92% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/ConditionApiProperties.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ConditionApiProperties.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/DmiPluginRegistration.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistration.java with 94% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/DmiPluginRegistrationResponse.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/NcmpServiceCmHandle.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java with 92% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/TrustLevel.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/UpgradedCmHandles.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/UpgradedCmHandles.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/models/YangResource.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/YangResource.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/CpsApplicationContext.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContext.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/DmiHttpClientConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/KafkaConfig.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfig.java with 81% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/OpenTelemetryConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/ServiceConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/NcmpStartUpException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NcmpStartUpException.java with 92% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/exceptions/NoAlternateIdMatchFoundException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NoAlternateIdParentFoundException.java with 82% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfig.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfig.java with 83% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/DmiCacheHandler.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avc/AvcEventConsumer.java with 72% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiCmSubscriptionDetailsPerDmiMapper.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiInEventMapper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapper.java with 57% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiInEventProducer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java with 51% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiOutEventConsumer.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/CmSubscriptionStatus.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmNotificationSubscriptionStatus.java with 85% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionDetails.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/DmiCmNotificationSubscriptionDetails.java with 78% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionKey.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionPredicate.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/DmiCmNotificationSubscriptionPredicate.java with 86% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionTuple.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparator.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionHandler.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerService.java with 58% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionHandlerImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpInEventConsumer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java with 50% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventMapper.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventProducer.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventPublishingTask.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java with 50% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandler.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpPassthroughResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryService.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImpl.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImpl.java with 74% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/async/AsyncRestRequestResponseEventConsumer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/AsyncRestRequestResponseEventConsumer.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumer.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapper.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategies.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategies.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/models/DmiDataOperation.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java with 88% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/models/DmiDataOperationRequest.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationRequest.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/models/DmiOperationCmHandle.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java with 65% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/utils/DataOperationEventCreator.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java with 73% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiProperties.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiServiceNameOrganizer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java with 94% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfiguration.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidator.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidator.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueries.java with 73% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImpl.java with 79% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java with 60% similarity, mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandler.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java with 86% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CompositeStateUtils.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CompositeStateUtils.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/DataStoreSyncState.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/DataStoreSyncState.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java with 89% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java with 81% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistence.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/ncmppersistence/NcmpPersistence.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistenceImpl.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/NcmpPersistenceImpl.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java with 77% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java with 73% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditions.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditions.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/CmHandleState.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/CmHandleState.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditions.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditions.java with 90% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/LockReasonCategory.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/LockReasonCategory.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/ModelledDmiServiceLeaves.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/ModelledDmiServiceLeaves.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/PropertyType.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/enums/PropertyType.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandle.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java with 92% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutor.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdog.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdog.java with 85% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java with 81% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtils.java with 59% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java with 71% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java with 76% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdog.java with 92% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java with 98% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurer.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventHeaderMapper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventHeaderMapper.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventType.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventType.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandler.java with 91% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java with 91% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImpl.java with 91% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCreator.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreator.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCreatorHelper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorHelper.java with 96% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsService.java with 50% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumer.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java with 70% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDog.java with 71% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManager.java with 56% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/DmiRequestBody.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java with 93% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/RequiredDmiService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/RequiredDmiService.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfiguration.java [moved from dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/config/NcmpRequestLoggingConfig.java with 52% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/EventDateTimeFormatter.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatter.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/YangDataConverter.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java with 78% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/UrlTemplateParameters.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/ModelLoader.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/CloudEventMapper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/mapper/CloudEventMapper.java with 97% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/CmAvcEventPublisher.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avc/ncmptoclient/AvcEventPublisher.java with 95% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/NcmpEvent.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/NcmpEvent.java with 93% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/TopicValidator.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java with 94% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandlerSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandlerSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy with 93% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy with 85% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/TrustLevelSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy with 93% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/CpsApplicationContextSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/KafkaConfigSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/kafka/KafkaConfigSpec.groovy with 95% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryConfigSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy with 51% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/CmSubscriptionConfigSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy with 61% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/DmiCacheHandlerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy with 51% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/cmavc/CmAvcEventConsumerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/AvcEventConsumerSpec.groovy with 88% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiCmSubscriptionDetailsPerDmiMapperSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiInEventMapperSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy with 63% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiInEventProducerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy with 51% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiOutEventConsumerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy with 59% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparatorSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionHandlerImplSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpInEventConsumerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy with 58% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventMapperSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventProducerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceServiceSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy with 60% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy [moved from cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerSpec.groovy with 56% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImplSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy with 89% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/FilterStrategiesIntegrationSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/FilterStrategiesIntegrationSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapperSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapperSpec.groovy with 98% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategiesSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategiesSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/SerializationIntegrationSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/SerializationIntegrationSpec.groovy with 96% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy with 71% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImplSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandlerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy with 75% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiPropertiesSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfigurationSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy with 53% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy with 82% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueriesImplSpec.groovy with 69% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy with 90% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy with 82% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy with 86% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy with 87% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy with 96% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy with 82% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy with 92% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy with 94% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy with 92% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy with 82% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy with 62% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy with 76% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy with 71% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy with 96% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy with 99% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy with 96% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy with 94% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCreatorSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy with 96% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsPublisherSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsPublisherSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsServiceSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsServiceSpec.groovy with 59% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy with 85% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy with 68% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfigSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfigSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy with 64% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfigurationSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/EventDateTimeFormatterSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/YangDataConverterSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy with 91% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilderSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/CmAvcEventPublisherSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/ncmptoclient/AvcEventPublisherSpec.groovy with 83% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/ConsumerBaseSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/ConsumerBaseSpec.groovy with 97% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/MessagingBaseSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy with 98% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/events/TopicValidatorSpec.groovy [moved from cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy with 94% similarity]
cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java [new file with mode: 0644]
cps-ncmp-service/src/test/resources/application.yml
cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json
cps-ncmp-service/src/test/resources/sampleAvcInputEvent.json
cps-parent/pom.xml
cps-path-parser/pom.xml
cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
cps-rest/docs/openapi/components.yml
cps-rest/docs/openapi/cpsData.yml
cps-rest/docs/openapi/cpsDataV2.yml
cps-rest/docs/openapi/openapi.yml
cps-rest/pom.xml
cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
cps-ri/pom.xml
cps-ri/src/main/java/org/onap/cps/ri/CpsAdminPersistenceServiceImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java with 87% similarity]
cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/CpsModulePersistenceServiceImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java with 95% similarity]
cps-ri/src/main/java/org/onap/cps/ri/models/AnchorEntity.java [moved from cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/models/DataspaceEntity.java [moved from cps-ri/src/main/java/org/onap/cps/spi/entities/DataspaceEntity.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/models/FragmentEntity.java [moved from cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/models/SchemaSetEntity.java [moved from cps-ri/src/main/java/org/onap/cps/spi/entities/SchemaSetEntity.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/models/YangResourceEntity.java [moved from cps-ri/src/main/java/org/onap/cps/spi/entities/YangResourceEntity.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/models/YangResourceModuleReference.java [moved from cps-ri/src/main/java/org/onap/cps/spi/entities/YangResourceModuleReference.java with 96% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/AnchorRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java with 97% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/DataspaceRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/DataspaceRepository.java with 95% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentPrefetchRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepository.java with 93% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentPrefetchRepositoryImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java with 97% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java with 62% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java with 95% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQuery.java with 89% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQueryImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java with 88% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceQuery.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java with 75% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java with 92% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java [new file with mode: 0644]
cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java with 96% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetYangResourceRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetYangResourceRepository.java with 96% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetYangResourceRepositoryImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetYangResourceRepositoryImpl.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/TempTableCreator.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceNativeRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepository.java with 96% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceNativeRepositoryImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java with 98% similarity]
cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceRepository.java [moved from cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceRepository.java with 96% similarity]
cps-ri/src/main/java/org/onap/cps/ri/utils/CpsSessionFactory.java [moved from cps-ri/src/main/java/org/onap/cps/spi/config/CpsSessionFactory.java with 90% similarity]
cps-ri/src/main/java/org/onap/cps/ri/utils/CpsValidatorImpl.java [moved from cps-ri/src/main/java/org/onap/cps/spi/impl/utils/CpsValidatorImpl.java with 97% similarity]
cps-ri/src/main/java/org/onap/cps/ri/utils/EscapeUtils.java [moved from cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java with 97% similarity]
cps-ri/src/main/java/org/onap/cps/ri/utils/SessionManager.java [moved from cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java with 96% similarity]
cps-ri/src/main/java/org/onap/cps/ri/utils/TimeLimiterProvider.java [moved from cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java with 97% similarity]
cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java [deleted file]
cps-ri/src/main/resources/changelog/db/changes/data/dmi/generated-csv/README.md [deleted file]
cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2021-12-13.yang [deleted file]
cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-02-10.yang [deleted file]
cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-05-10.yang [deleted file]
cps-ri/src/main/resources/yangResourceCsvGenerator.py [deleted file]
cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy [moved from cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy with 97% similarity]
cps-ri/src/test/groovy/org/onap/cps/ri/CpsModulePersistenceServiceConcurrencySpec.groovy [moved from cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy with 95% similarity]
cps-ri/src/test/groovy/org/onap/cps/ri/CpsModulePersistenceServiceImplSpec.groovy [moved from cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceSpec.groovy with 93% similarity]
cps-ri/src/test/groovy/org/onap/cps/ri/utils/CpsValidatorImplSpec.groovy [moved from cps-ri/src/test/groovy/org/onap/cps/spi/impl/utils/CpsValidatorSpec.groovy with 97% similarity]
cps-ri/src/test/groovy/org/onap/cps/ri/utils/EscapeUtilsSpec.groovy [moved from cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy with 98% similarity]
cps-ri/src/test/groovy/org/onap/cps/ri/utils/SessionManagerSpec.groovy [moved from cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy with 95% similarity]
cps-service/pom.xml
cps-service/src/main/java/org/onap/cps/api/CpsAnchorService.java
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java
cps-service/src/main/java/org/onap/cps/impl/utils/CpsValidator.java [moved from cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java with 97% similarity]
cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java
cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java
cps-service/src/main/java/org/onap/cps/utils/YangParser.java
cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy
cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
csit/install-deps.sh [new file with mode: 0755]
csit/plans/cps/pnfsim/docker-compose.yml
csit/plans/cps/pnfsim/tls/ca.pem [new file with mode: 0644]
csit/plans/cps/pnfsim/tls/server_cert.pem [new file with mode: 0644]
csit/plans/cps/pnfsim/tls/server_key.pem [new file with mode: 0644]
csit/plans/cps/sdnc/certs/keys0.zip
csit/plans/cps/setup.sh
csit/plans/cps/teardown.sh
csit/plans/cps/test.properties
csit/plans/cps/testplanNcmp.txt
csit/prepare-csit.sh
csit/pylibs.txt
csit/run-csit.sh
csit/run-project-csit.sh
csit/tests/cps-data-operations/cps-data-operations.robot
csit/tests/cps-data-sync/cps-data-sync.robot
csit/tests/cps-model-sync/cps-model-sync.robot
csit/tests/cps-trust-level/cps-trust-level.robot
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/CmHandle.java [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DmiDataOperationRequest.java [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/utils/ResourceFileReaderUtil.java [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/data/operational/ietf-network-topology-sample-rfc8345.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/ietfYang-ModuleResourcesResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/ietfYang-ModuleResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagA-ModuleResourcesResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagA-ModuleResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagB-ModuleResourcesResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagB-ModuleResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagC-ModuleResourcesResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagC-ModuleResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagD-ModuleResourcesResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagD-ModuleResponse.json [deleted file]
dmi-plugin-demo-and-csit-stub/pom.xml [deleted file]
docker-compose/config/grafana/jvm-micrometer-dashboard.json [new file with mode: 0644]
docker-compose/config/grafana/provisioning/dashboards/dashboard.yml [new file with mode: 0644]
docker-compose/config/grafana/provisioning/datasources/datasource.yml [new file with mode: 0644]
docker-compose/config/nginx/nginx.conf [new file with mode: 0644]
docker-compose/config/nginx/proxy_params [new file with mode: 0644]
docker-compose/config/postgres-init.sql [moved from docker-compose/postgres-init.sql with 100% similarity]
docker-compose/config/prometheus.yml [moved from docker-compose/prometheus.yml with 53% similarity]
docker-compose/docker-compose.yml
docs/_static/cps-delta-mechanism.png
docs/_static/logo_onap_2024.png [new file with mode: 0644]
docs/admin-guide.rst
docs/api/swagger/cps/openapi.yaml
docs/api/swagger/ncmp/openapi-inventory.yaml
docs/api/swagger/ncmp/openapi.yaml
docs/api/swagger/policy-executor/openapi.yaml [new file with mode: 0644]
docs/cm-notification-subscriptions.rst [new file with mode: 0644]
docs/conf.py
docs/cps-delta-endpoints.rst
docs/cps-delta-feature.rst
docs/cps-events.rst
docs/cps-ncmp-message-status-codes.rst
docs/design.rst
docs/ncmp-inventory-querying.rst
docs/policy-executor.rst [new file with mode: 0644]
docs/release-notes.rst
docs/schemas/ncmp-in-event-schema-1.0.0.json [new file with mode: 0644]
docs/schemas/ncmp-out-event-schema-1.0.0.json [moved from cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/cm-notification-subscription-ncmp-out-event-schema-1.0.0.json with 72% similarity]
docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json [new file with mode: 0644]
docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json [new file with mode: 0644]
docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json [new file with mode: 0644]
docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json [new file with mode: 0644]
integration-test/pom.xml
integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy
integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/AnchorServiceIntegrationSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAnchorServiceIntegrationSpec.groovy with 92% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy with 87% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataspaceServiceIntegrationSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataspaceServiceIntegrationSpec.groovy with 96% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy with 99% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy with 99% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/SessionManagerIntegrationSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/SessionManagerIntegrationSpec.groovy with 97% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy with 74% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy with 50% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy with 67% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmNotificationSubscriptionSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy with 56% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DmiUrlEncodingPassthroughSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy with 66% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataspaceServiceLimitsPerfTest.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
integration-test/src/test/java/org/onap/cps/integration/DmiStubTestContainer.java [new file with mode: 0644]
integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java
integration-test/src/test/resources/application.yml
jacoco-report/pom.xml
k6-tests/README.md [new file with mode: 0644]
k6-tests/install-deps.sh [new file with mode: 0755]
k6-tests/ncmp/common/cmhandle-crud.js [new file with mode: 0644]
k6-tests/ncmp/common/passthrough-crud.js [new file with mode: 0644]
k6-tests/ncmp/common/search-base.js [new file with mode: 0644]
k6-tests/ncmp/common/utils.js [new file with mode: 0644]
k6-tests/ncmp/ncmp-kpi.js [new file with mode: 0644]
k6-tests/ncmp/run-all-tests.sh [new file with mode: 0755]
k6-tests/run-k6-tests.sh [new file with mode: 0755]
k6-tests/setup.sh [new file with mode: 0755]
k6-tests/teardown.sh [new file with mode: 0755]
policy-executor-stub/pom.xml [new file with mode: 0644]
policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/PolicyExecutorApplication.java [moved from dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/DmiDemoApplication.java with 78% similarity]
policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java [new file with mode: 0644]
policy-executor-stub/src/main/resources/application.yml [new file with mode: 0644]
policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/PolicyExecutorApplicationSpec.groovy [new file with mode: 0644]
policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy [new file with mode: 0644]
pom.xml
postman-collections/CPS Environment.postman_environment.json
postman-collections/DMI Stub.postman_collection.json [deleted file]
releases/3.5.0-container.yaml [new file with mode: 0644]
releases/3.5.0.yaml [new file with mode: 0644]
releases/3.5.1-container.yaml [new file with mode: 0644]
releases/3.5.1.yaml [new file with mode: 0644]
releases/3.5.2-container.yaml [new file with mode: 0644]
releases/3.5.2.yaml [new file with mode: 0644]
spotbugs/pom.xml
spotbugs/src/main/resources/spotbugs-exclude.xml
version.properties

index 9ecfa2d..f618836 100644 (file)
@@ -26,7 +26,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>checkstyle</artifactId>
-    <version>3.5.0-SNAPSHOT</version>
+    <version>3.5.3-SNAPSHOT</version>
 
     <profiles>
         <profile>
index 6804c7d..19710be 100644 (file)
@@ -28,7 +28,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
@@ -75,7 +75,7 @@
         </dependency>
         <dependency>
             <groupId>io.micrometer</groupId>
-            <artifactId>micrometer-tracing-bridge-brave</artifactId>
+            <artifactId>micrometer-tracing-bridge-otel</artifactId>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.dataformat</groupId>
         </dependency>
 
         <!-- T E S T   D E P E N D E N C I E S -->
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-test</artifactId>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>org.codehaus.groovy</groupId>
             <artifactId>groovy</artifactId>
         <dependency>
             <groupId>com.tngtech.archunit</groupId>
             <artifactId>archunit-junit5</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
index 8100680..25bc63b 100644 (file)
@@ -101,10 +101,10 @@ app:
         async-m2m:
             topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
         avc:
-            subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:subscription}
-            subscription-forward-topic-prefix: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-}
-            subscription-response-topic: ${NCMP_RESPONSE_CM_AVC_SUBSCRIPTION:dmi-ncmp-cm-avc-subscription}
-            subscription-outcome-topic: ${NCMP_OUTCOME_CM_AVC_SUBSCRIPTION:subscription-response}
+            cm-subscription-ncmp-in: ${CM_SUBSCRIPTION_NCMP_IN_TOPIC:subscription}
+            cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription}
+            cm-subscription-dmi-out: ${CM_SUBSCRIPTION_DMI_OUT_TOPIC:dmi-ncmp-cm-avc-subscription}
+            cm-subscription-ncmp-out: ${CM_SUBSCRIPTION_NCMP_OUT_TOPIC:subscription-response}
             cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}
     lcm:
         events:
@@ -151,8 +151,24 @@ security:
         username: ${CPS_USERNAME:cpsuser}
         password: ${CPS_PASSWORD:cpsr0cks!}
 
+cps:
+    tracing:
+        sampler:
+            jaeger_remote:
+                endpoint: ${ONAP_OTEL_SAMPLER_JAEGER_REMOTE_ENDPOINT:http://onap-otel-collector:14250}
+        exporter:
+            endpoint: ${ONAP_OTEL_EXPORTER_ENDPOINT:http://onap-otel-collector:4317}
+            protocol: ${ONAP_OTEL_EXPORTER_PROTOCOL:grpc}
+        enabled: ${ONAP_TRACING_ENABLED:false}
+        excluded-observation-names: ${ONAP_EXCLUDED_OBSERVATION_NAMES:tasks.scheduled.execution}
+
 # Actuator
 management:
+    tracing:
+        propagation:
+            produce: ${ONAP_PROPAGATOR_PRODUCE:[W3C]}
+        sampling:
+            probability: 1.0
     endpoints:
         web:
             exposure:
@@ -172,12 +188,35 @@ logging:
             onap:
                 cps: INFO
 ncmp:
+    policy-executor:
+        enabled: ${POLICY_SERVICE_ENABLED:false}
+        server:
+            address: ${POLICY_SERVICE_URL:http://policy-executor-stub}
+            port: ${POLICY_SERVICE_PORT:8093}
+        httpclient:
+            all-services:
+                maximumInMemorySizeInMegabytes: 16
+                maximumConnectionsTotal: 100
+                pendingAcquireMaxCount: 50
+                connectionTimeoutInSeconds: 30
+                readTimeoutInSeconds: 30
+                writeTimeoutInSeconds: 30
     dmi:
         httpclient:
-            connectionTimeoutInSeconds: 30
-            maximumConnectionsPerRoute: 50
-            maximumConnectionsTotal: 100
-            idleConnectionEvictionThresholdInSeconds: 5
+            data-services:
+                maximumInMemorySizeInMegabytes: 16
+                maximumConnectionsTotal: 100
+                pendingAcquireMaxCount: 50
+                connectionTimeoutInSeconds: 30
+                readTimeoutInSeconds: 30
+                writeTimeoutInSeconds: 30
+            model-services:
+                maximumInMemorySizeInMegabytes: 16
+                maximumConnectionsTotal: 100
+                pendingAcquireMaxCount: 50
+                connectionTimeoutInSeconds: 30
+                readTimeoutInSeconds: 30
+                writeTimeoutInSeconds: 30
         auth:
             username: ${DMI_USERNAME:cpsuser}
             password: ${DMI_PASSWORD:cpsr0cks!}
@@ -189,7 +228,7 @@ ncmp:
         advised-modules-sync:
             sleep-time-ms: 5000
         locked-modules-sync:
-            sleep-time-ms: 300000
+            sleep-time-ms: 15000
         cm-handle-data-sync:
             sleep-time-ms: 30000
         subscription-forwarding:
@@ -213,3 +252,9 @@ hazelcast:
         kubernetes:
             enabled: ${HAZELCAST_MODE_KUBERNETES_ENABLED:false}
             service-name: ${CPS_NCMP_SERVICE_NAME:"cps-and-ncmp-service"}
+
+otel:
+    exporter:
+        otlp:
+            traces:
+                protocol: ${ONAP_OTEL_EXPORTER_OTLP_TRACES_PROTOCOL:grpc}
index c18a3ed..82fdc7f 100644 (file)
@@ -25,6 +25,7 @@ import static com.tngtech.archunit.library.freeze.FreezingArchRule.freeze;
 
 import com.tngtech.archunit.core.importer.ImportOption;
 import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchIgnore;
 import com.tngtech.archunit.junit.ArchTest;
 import com.tngtech.archunit.lang.ArchRule;
 
@@ -37,9 +38,9 @@ public class LayeredArchitectureTest {
     private static final String REST_CONTROLLER_PACKAGE = "org.onap.cps.rest..";
     private static final String NCMP_REST_PACKAGE = "org.onap.cps.ncmp.rest..";
     private static final String API_SERVICE_PACKAGE = "org.onap.cps.api..";
-    private static final String SPI_SERVICE_PACKAGE = "org.onap.cps.spi..";
+    private static final String SPI_SERVICE_PACKAGE = "org.onap.cps.ri..";
     private static final String NCMP_SERVICE_PACKAGE = "org.onap.cps.ncmp.api..";
-    private static final String SPI_REPOSITORY_PACKAGE = "org.onap.cps.spi.repository..";
+    private static final String SPI_REPOSITORY_PACKAGE = "org.onap.cps.ri.repository..";
     private static final String YANG_SCHEMA_PACKAGE = "org.onap.cps.yang..";
     private static final String NOTIFICATION_PACKAGE = "org.onap.cps.notification..";
     private static final String CPS_UTILS_PACKAGE = "org.onap.cps.utils..";
@@ -47,12 +48,15 @@ public class LayeredArchitectureTest {
     private static final String CPS_CACHE_PACKAGE = "org.onap.cps.cache..";
     private static final String CPS_EVENTS_PACKAGE = "org.onap.cps.events..";
 
+    //TODO We need to revisit these rules, the first one doesn't even make any sense: CPS-2293
+
     @ArchTest
     static final ArchRule restControllerShouldOnlyDependOnRestController =
         classes().that().resideInAPackage(REST_CONTROLLER_PACKAGE).should().onlyHaveDependentClassesThat()
             .resideInAPackage(REST_CONTROLLER_PACKAGE);
 
     @ArchTest
+    @ArchIgnore
     static final ArchRule apiOrSpiServiceShouldOnlyBeDependedOnByControllerAndServicesAndCommonUtilityPackages =
         freeze(classes().that().resideInAPackage(API_SERVICE_PACKAGE)
             .or().resideInAPackage(SPI_SERVICE_PACKAGE).should().onlyHaveDependentClassesThat()
index 4548eb2..31aedab 100644 (file)
@@ -25,7 +25,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-bom</artifactId>
-    <version>3.5.0-SNAPSHOT</version>
+    <version>3.5.3-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <description>This artifact contains dependencyManagement declarations of all published CPS components.</description>
index effaa06..adef903 100644 (file)
@@ -27,7 +27,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-dependencies</artifactId>
-    <version>3.5.0-SNAPSHOT</version>
+    <version>3.5.3-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <name>${project.groupId}:${project.artifactId}</name>
@@ -42,6 +42,7 @@
         <testcontainers.version>1.18.3</testcontainers.version>
         <mapstruct.version>1.4.2.Final</mapstruct.version>
         <jetty-version>11.0.16</jetty-version>
+        <version.opentelemetry-instrumentation-bom>2.1.0-alpha</version.opentelemetry-instrumentation-bom>
     </properties>
 
     <build>
                 <artifactId>spotbugs</artifactId>
                 <version>4.2.3</version>
             </dependency>
+            <dependency>
+                <groupId>com.google.code.findbugs</groupId>
+                <artifactId>annotations</artifactId>
+                <version>3.0.1</version>
+            </dependency>
             <dependency>
                 <groupId>com.google.code.gson</groupId>
                 <artifactId>gson</artifactId>
             <dependency>
                 <groupId>com.hazelcast</groupId>
                 <artifactId>hazelcast-spring</artifactId>
-                <version>5.3.1</version>
+                <version>5.3.7</version>
             </dependency>
             <dependency>
                 <groupId>com.squareup.okhttp3</groupId>
                 <version>3.7.3</version>
             </dependency>
             <dependency>
-                <groupId>io.micrometer</groupId>
-                <artifactId>micrometer-tracing-bridge-brave</artifactId>
-                <version>1.0.0</version>
+                <groupId>io.opentelemetry.instrumentation</groupId>
+                <artifactId>opentelemetry-instrumentation-bom-alpha</artifactId>
+                <version>${version.opentelemetry-instrumentation-bom}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>io.opentelemetry</groupId>
+                <artifactId>opentelemetry-bom</artifactId>
+                <version>1.37.0</version>
+                <type>pom</type>
+                <scope>import</scope>
             </dependency>
             <dependency>
                 <groupId>io.swagger.core.v3</groupId>
             <dependency>
                 <groupId>org.liquibase</groupId>
                 <artifactId>liquibase-core</artifactId>
-                <version>4.21.0</version>
+                <version>4.29.0</version>
             </dependency>
             <dependency>
                 <groupId>org.mapstruct</groupId>
             <dependency>
                 <groupId>org.projectlombok</groupId>
                 <artifactId>lombok</artifactId>
-                <version>1.18.24</version>
+                <version>1.18.32</version>
             </dependency>
             <dependency>
                 <groupId>org.testcontainers</groupId>
index ec0c96b..e5fab86 100644 (file)
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 3804e5b..b0b2899 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
@@ -1,12 +1,12 @@
 {
   "$schema": "https://json-schema.org/draft/2019-09/schema",
   "$id": "urn:cps:org.onap.cps.ncmp.events:cm-notification-subscription-dmi-in-event-schema:1.0.0",
-  "$ref": "#/definitions/CmNotificationSubscriptionDmiInEvent",
+  "$ref": "#/definitions/DmiInEvent",
   "definitions": {
-    "CmNotificationSubscriptionDmiInEvent": {
+    "DmiInEvent": {
       "description": "The payload for cm notification subscription event incoming message from NCMP.",
       "type": "object",
-      "javaType": "org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent",
+      "javaType": "org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.DmiInEvent",
       "additionalProperties": false,
       "properties": {
         "data": {
@@ -22,7 +22,7 @@
       "description": "Information about the targets and subscription",
       "additionalProperties": false,
       "properties": {
-        "cmhandles": {
+        "cmHandles": {
           "type": "array",
           "items": {
             "type": "object",
@@ -32,7 +32,7 @@
               "cmhandleId": {
                 "type": "string"
               },
-              "private-properties": {
+              "privateProperties": {
                 "type": "object",
                 "existingJavaType": "java.util.Map<String,String>",
                 "items": {
@@ -63,7 +63,7 @@
                     "type": "string",
                     "enum": ["ncmp-datastore:passthrough-operational", "ncmp-datastore:passthrough-running"]
                   },
-                  "xpath-filter": {
+                  "xpathFilter": {
                     "description": "Filter to be applied to the CM Handles through this event",
                     "type": "array",
                     "items": {
@@ -73,7 +73,7 @@
                 },
                 "additionalProperties": false,
                 "required": [
-                  "xpath-filter"
+                  "xpathFilter"
                 ]
               }
             },
@@ -86,8 +86,7 @@
         }
       },
       "required": [
-        "cmhandles",
-        "predicates"
+        "cmHandles"
       ]
     }
   }
@@ -1,13 +1,13 @@
 {
   "$schema": "https://json-schema.org/draft/2019-09/schema",
   "$id": "urn:cps:org.onap.cps.ncmp.events:cm-notification-subscription-dmi-out-event-schema:1.0.0",
-  "$ref": "#/definitions/CmNotificationSubscriptionDmiOutEvent",
+  "$ref": "#/definitions/DmiOutEvent",
   "definitions": {
-    "CmNotificationSubscriptionDmiOutEvent": {
+    "DmiOutEvent": {
       "description": "The payload for cm notification subscription merge event coming out from DMI Plugin.",
       "type": "object",
       "additionalProperties": false,
-      "javaType": "org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent",
+      "javaType": "org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.dmi_to_ncmp.DmiOutEvent",
       "properties": {
         "data": {
           "$ref": "#/definitions/Data"
@@ -16,7 +16,7 @@
       "required": [
         "data"
       ],
-      "title": "CmNotificationSubscriptionDmiOutEvent"
+      "title": "DmiOutEvent"
     },
     "Data": {
       "type": "object",
@@ -1,11 +1,11 @@
 {
   "$id": "urn:cps:org.onap.cps.ncmp.events:cm-notification-subscription-ncmp-in-event:1.0.0",
-  "$ref": "#/definitions/CmNotificationSubscriptionNcmpInEvent",
+  "$ref": "#/definitions/NcmpInEvent",
   "$schema": "https://json-schema.org/draft/2019-09/schema",
   "definitions": {
-    "CmNotificationSubscriptionNcmpInEvent": {
+    "NcmpInEvent": {
       "description": "The payload for subscription merge event.",
-      "javaType": "org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent",
+      "javaType": "org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.NcmpInEvent",
       "properties": {
         "data": {
           "properties": {
@@ -34,7 +34,7 @@
                         "type": "string",
                         "enum": ["ncmp-datastore:passthrough-operational", "ncmp-datastore:passthrough-running"]
                       },
-                      "xpath-filter": {
+                      "xpathFilter": {
                         "description": "Filter to be applied to the CM Handles through this event",
                         "type": "array",
                         "items": {
@@ -44,7 +44,7 @@
                     },
                     "additionalProperties": false,
                     "required": [
-                      "xpath-filter"
+                      "xpathFilter"
                     ]
                   }
                 },
@@ -57,8 +57,7 @@
             }
           },
           "required": [
-            "subscriptionId",
-            "predicates"
+            "subscriptionId"
           ],
           "type": "object",
           "additionalProperties": false
diff --git a/cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/ncmp-out-event-schema-1.0.0.json b/cps-ncmp-events/src/main/resources/schemas/cmnotificationsubscription/ncmp-out-event-schema-1.0.0.json
new file mode 100644 (file)
index 0000000..11dc4e1
--- /dev/null
@@ -0,0 +1,57 @@
+{
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "$id": "urn:cps:org.onap.cps.ncmp.events:cm-notification-subscription-ncmp-out-event-schema:1.0.0",
+  "$ref": "#/definitions/NcmpOutEvent",
+  "definitions": {
+    "NcmpOutEvent": {
+      "type": "object",
+      "description": "The payload applied cm subscription merge event coming out from NCMP.",
+      "javaType": "org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent",
+      "additionalProperties": false,
+      "properties": {
+        "data": {
+          "$ref": "#/definitions/Data"
+        }
+      },
+      "required": [
+        "data"
+      ],
+      "title": "NcmpOutEvent"
+    },
+    "Data": {
+      "type": "object",
+      "description": "Information about the targets and subscription",
+      "additionalProperties": false,
+      "properties": {
+        "subscriptionId": {
+          "type": "string",
+          "description": "The unique subscription id"
+        },
+        "acceptedTargets": {
+          "type": "object",
+          "existingJavaType": "java.util.Collection<String>",
+          "description": "Unique Collection of accepted targets"
+        },
+        "rejectedTargets": {
+          "type": "object",
+          "existingJavaType": "java.util.Collection<String>",
+          "description": "Unique Collection of rejected targets"
+        },
+        "pendingTargets": {
+          "type": "object",
+          "existingJavaType": "java.util.Collection<String>",
+          "description": "Unique Collection of pending targets"
+        }
+      },
+      "required": [
+        "subscriptionId",
+        "acceptedTargets",
+        "rejectedTargets",
+        "pendingTargets"
+      ],
+      "title": "Data"
+    }
+  }
+
+
+}
\ No newline at end of file
index a5bed93..474520d 100644 (file)
@@ -16,7 +16,8 @@
           "type": "string"
         },
         "value": {
-          "$ref": "#/definitions/Value"
+          "type": "object",
+          "existingJavaType": "java.lang.Object"
         }
       },
       "required": [
         "target"
       ]
     },
-    "Value": {
-      "type": "object",
-      "additionalProperties": false,
-      "properties": {
-        "attributes": {
-          "type": "array",
-          "items": {
-            "type": "object",
-            "existingJavaType": "java.util.Map<String,Object>",
-            "additionalProperties": false,
-            "properties": {
-              "isHoAllowed": {
-                "type": "boolean"
-              }
-            }
-          }
-        }
-      }
-    },
     "AvcEvent": {
       "description": "The payload for AVC event.",
       "type": "object",
index 7b277cd..c73a251 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-ncmp-rest-stub</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
     </parent>
 
     <artifactId>cps-ncmp-rest-stub-app</artifactId>
index cb65532..eee0431 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-ncmp-rest-stub</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
     </parent>
     <artifactId>cps-ncmp-rest-stub-service</artifactId>
 
index 08a492e..183698c 100644 (file)
@@ -35,7 +35,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
 import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
 import org.onap.cps.ncmp.rest.model.DataOperationRequest;
@@ -165,7 +165,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(final String cmHandle) {
+    public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(
+            final String cmHandleReference) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
index b36e25a..177febd 100644 (file)
  */
 package org.onap.cps.ncmp.rest.stub
 
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType
+import com.fasterxml.jackson.core.JsonProcessingException
+import com.fasterxml.jackson.core.type.TypeReference
+import com.fasterxml.jackson.databind.JsonMappingException
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.data.models.DatastoreType
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration
-import org.springframework.boot.test.context.SpringBootContextLoader
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.boot.test.web.client.TestRestTemplate
 import org.springframework.boot.test.web.server.LocalServerPort
 import org.springframework.http.HttpStatus
 import org.springframework.test.context.ActiveProfiles
-import org.springframework.test.context.ContextConfiguration
-
-import com.fasterxml.jackson.core.JsonProcessingException
-import com.fasterxml.jackson.core.type.TypeReference
-import com.fasterxml.jackson.databind.JsonMappingException
-import com.fasterxml.jackson.databind.ObjectMapper
-
-import spock.lang.Shared
 import spock.lang.Specification
 
 @SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
index 441ce43..01604e8 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 8aa38c0..112dddf 100644 (file)
@@ -505,6 +505,14 @@ components:
       schema:
         type: string
         example: my-cm-handle
+    cmHandleReferenceInPath:
+      name: cm-handle
+      in: path
+      description: The identifier (cmHandle or alternate) for a network function, network element, subnetwork or any other cm object by managed Network CM Proxy
+      required: true
+      schema:
+        type: string
+        example: my-cm-handle-reference
     moduleNameInQuery:
       name: module-name
       in: query
@@ -578,7 +586,6 @@ components:
       in: query
       description: The format of resource identifier depend on the associated DMI Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but it can really be anything.
       required: true
-      allowReserved: true
       schema:
         type: string
       examples:
@@ -598,7 +605,6 @@ components:
       required: false
       schema:
         type: string
-      allowReserved: true
       examples:
         sample 1:
           value:
@@ -616,7 +622,6 @@ components:
       required: false
       schema:
         type: string
-      allowReserved: true
       examples:
         sample 1:
           value:
@@ -628,7 +633,6 @@ components:
       required: true
       schema:
         type: string
-      allowReserved: true
       examples:
         sample 1:
           value:
index d0b1f35..adb2419 100755 (executable)
@@ -27,7 +27,7 @@ resourceDataForCmHandle:
     operationId: getResourceDataForCmHandle
     parameters:
       - $ref: 'components.yaml#/components/parameters/datastoreName'
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/optionsParamInQuery'
       - $ref: 'components.yaml#/components/parameters/topicParamInQuery'
@@ -60,7 +60,7 @@ resourceDataForCmHandle:
     operationId: createResourceDataRunningForCmHandle
     parameters:
       - $ref: 'components.yaml#/components/parameters/datastoreName'
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
       - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader'
@@ -99,7 +99,7 @@ resourceDataForCmHandle:
     operationId: updateResourceDataRunningForCmHandle
     parameters:
       - $ref: 'components.yaml#/components/parameters/datastoreName'
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
       - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader'
@@ -138,7 +138,7 @@ resourceDataForCmHandle:
     operationId: patchResourceDataRunningForCmHandle
     parameters:
       - $ref: 'components.yaml#/components/parameters/datastoreName'
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
       - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader'
@@ -171,7 +171,7 @@ resourceDataForCmHandle:
     operationId: deleteResourceDataRunningForCmHandle
     parameters:
       - $ref: 'components.yaml#/components/parameters/datastoreName'
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
       - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader'
@@ -194,7 +194,7 @@ dataOperationForCmHandle:
     tags:
       - network-cm-proxy
     summary: Execute a data operation for group of cm handle ids
-    description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages. A maximum of 50 cm handles per operation is supported.
+    description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages. A maximum of 200 cm handles per operation is supported.
     operationId: executeDataOperationForCmHandles
     parameters:
       - $ref: 'components.yaml#/components/parameters/requiredTopicParamInQuery'
@@ -264,7 +264,7 @@ fetchModuleReferencesByCmHandle:
     summary: Fetch all module references (name and revision) for a given cm handle
     operationId: getModuleReferencesByCmHandle
     parameters:
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
     responses:
       200:
         description: OK
@@ -289,7 +289,7 @@ getModuleDefinitions:
     description: Get module definitions (module name, revision, yang resource) with options to filter on module name and revision
     operationId: getModuleDefinitions
     parameters:
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
       - $ref: 'components.yaml#/components/parameters/moduleNameInQuery'
       - $ref: 'components.yaml#/components/parameters/revisionInQuery'
     responses:
@@ -354,7 +354,7 @@ retrieveCmHandleDetailsById:
     summary: Retrieve CM handle details
     operationId: retrieveCmHandleDetailsById
     parameters:
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
     responses:
       200:
         description: OK
@@ -377,7 +377,7 @@ getCmHandlePropertiesById:
     summary: Get CM handle properties
     operationId: getCmHandlePublicPropertiesByCmHandleId
     parameters:
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
     responses:
       200:
         description: OK
@@ -400,7 +400,7 @@ getCmHandleStateById:
     summary: Get CM handle state
     operationId: getCmHandleStateByCmHandleId
     parameters:
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath'
     responses:
       200:
         description: OK
index b794082..8c0ad41 100755 (executable)
@@ -21,7 +21,7 @@ openapi: 3.0.3
 info:
   title: NCMP Inventory API
   description: NCMP Inventory API
-  version: "1.0"
+  version: "3.5.2"
 servers:
   - url: /ncmpInventory
 components:
@@ -40,4 +40,4 @@ paths:
     $ref: 'ncmp-inventory.yml#/searchCmHandleIds'
 
 security:
-  - basicAuth: []
\ No newline at end of file
+  - basicAuth: []
index dd6d7c8..78fb141 100755 (executable)
@@ -22,7 +22,7 @@ openapi: 3.0.3
 info:
   title: NCMP to CPS Proxy API
   description: NCMP to CPS Proxy API
-  version: "1.0"
+  version: "3.5.2"
 servers:
   - url: /ncmp
 components:
@@ -64,4 +64,4 @@ paths:
   /v1/ch/{cm-handle}/data-sync:
     $ref: 'ncmp.yml#/setDataSyncEnabledFlag'
 security:
-  - basicAuth: []
\ No newline at end of file
+  - basicAuth: []
index e333344..adac504 100644 (file)
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 45c7c33..42f709d 100755 (executable)
 
 package org.onap.cps.ncmp.rest.controller;
 
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL;
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE;
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE;
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH;
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE;
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL;
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING;
+import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
+import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE;
+import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH;
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE;
 
 import io.micrometer.core.annotation.Timed;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig;
-import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
-import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
-import org.onap.cps.ncmp.api.models.CmResourceAddress;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException;
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade;
 import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler;
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreRequestHandler;
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler;
-import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper;
-import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper;
 import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
 import org.onap.cps.ncmp.rest.model.DataOperationRequest;
@@ -61,10 +54,13 @@ import org.onap.cps.ncmp.rest.model.RestModuleReference;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties;
+import org.onap.cps.ncmp.rest.util.CmHandleStateMapper;
+import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper;
 import org.onap.cps.ncmp.rest.util.DeprecationHelper;
+import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper;
+import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.StringUtils;
@@ -78,22 +74,19 @@ import org.springframework.web.bind.annotation.RestController;
 public class NetworkCmProxyController implements NetworkCmProxyApi {
 
     private static final String NO_BODY = null;
-    private final NetworkCmProxyDataService networkCmProxyDataService;
+    private final NetworkCmProxyFacade networkCmProxyFacade;
+    private final NetworkCmProxyInventoryFacade networkCmProxyInventoryFacade;
     private final JsonObjectMapper jsonObjectMapper;
     private final DeprecationHelper deprecationHelper;
     private final NcmpRestInputMapper ncmpRestInputMapper;
     private final CmHandleStateMapper cmHandleStateMapper;
-    private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler;
-    private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler;
     private final DataOperationRequestMapper dataOperationRequestMapper;
-    @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_CM_HANDLE)
-    private final Map<String, TrustLevel> trustLevelPerCmHandle;
 
     /**
      * Get resource data from datastore.
      *
      * @param datastoreName        name of the datastore
-     * @param cmHandle             cm handle identifier
+     * @param cmHandleReference    cm handle or alternate id identifier
      * @param resourceIdentifier   resource identifier
      * @param optionsParamInQuery  options query parameter
      * @param topicParamInQuery    topic query parameter
@@ -104,36 +97,38 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     @Override
     @Timed(value = "cps.ncmp.controller.get", description = "Time taken to get resource data from datastore")
     public ResponseEntity<Object> getResourceDataForCmHandle(final String datastoreName,
-                                                             final String cmHandle,
+                                                             final String cmHandleReference,
                                                              final String resourceIdentifier,
                                                              final String optionsParamInQuery,
                                                              final String topicParamInQuery,
                                                              final Boolean includeDescendants,
                                                              final String authorization) {
-        final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler = getNcmpDatastoreRequestHandler(datastoreName);
-        final CmResourceAddress cmResourceAddress = new CmResourceAddress(datastoreName, cmHandle, resourceIdentifier);
-        return ncmpDatastoreRequestHandler.executeRequest(cmResourceAddress, optionsParamInQuery, topicParamInQuery,
-            includeDescendants, authorization);
+        final CmResourceAddress cmResourceAddress = new CmResourceAddress(datastoreName, cmHandleReference,
+            resourceIdentifier);
+        final Object result = networkCmProxyFacade.getResourceDataForCmHandle(cmResourceAddress, optionsParamInQuery,
+            topicParamInQuery, includeDescendants, authorization);
+        return ResponseEntity.ok(result);
     }
 
     @Override
     public ResponseEntity<Object> executeDataOperationForCmHandles(final String topicParamInQuery,
                                                                    final DataOperationRequest dataOperationRequest,
                                                                    final String authorization) {
-        return ncmpPassthroughResourceRequestHandler.executeRequest(topicParamInQuery,
+        final Object result = networkCmProxyFacade.executeDataOperationForCmHandles(topicParamInQuery,
                 dataOperationRequestMapper.toDataOperationRequest(dataOperationRequest), authorization);
+        return ResponseEntity.ok(result);
     }
 
     /**
      * Query resource data from datastore.
      *
-     * @param datastoreName        name of the datastore
+     * @param datastoreName        name of the datastore (currently only supports "ncmp-datastore:operational")
      * @param cmHandle             cm handle identifier
      * @param cpsPath              CPS Path
      * @param optionsParamInQuery  options query parameter
      * @param topicParamInQuery    topic query parameter
      * @param includeDescendants   whether to include descendants or not
-     * @return {@code ResponseEntity} response from dmi plugin
+     * @return {@code ResponseEntity} response. Body contains a collection of DataNodes
      */
 
     @Override
@@ -144,14 +139,16 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                                final String topicParamInQuery,
                                                                final Boolean includeDescendants) {
         validateDataStore(OPERATIONAL, datastoreName);
-        return ncmpCachedResourceRequestHandler.executeRequest(cmHandle, cpsPath, includeDescendants);
+        final Collection<DataNode> dataNodes = networkCmProxyFacade.queryResourceDataForCmHandle(cmHandle, cpsPath,
+            includeDescendants);
+        return ResponseEntity.ok(dataNodes);
     }
 
     /**
-     * Patch resource data from passthrough-running.
+     * Patch resource data.
      *
-     * @param datastoreName      name of the datastore
-     * @param cmHandle           cm handle identifier
+     * @param datastoreName      name of the datastore (currently only supports "ncmp-datastore:passthrough-running")
+     * @param cmHandleReference  cm handle or alternate identifier
      * @param resourceIdentifier resource identifier
      * @param requestBody        the request body
      * @param contentType        content type of body
@@ -161,7 +158,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
 
     @Override
     public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName,
-                                                                      final String cmHandle,
+                                                                      final String cmHandleReference,
                                                                       final String resourceIdentifier,
                                                                       final Object requestBody,
                                                                       final String contentType,
@@ -169,18 +166,18 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
 
         validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
-        final Object responseObject = networkCmProxyDataService
+        final Object responseObject = networkCmProxyFacade
                 .writeResourceDataPassThroughRunningForCmHandle(
-                        cmHandle, resourceIdentifier, PATCH,
+                        cmHandleReference, resourceIdentifier, PATCH,
                         jsonObjectMapper.asJsonString(requestBody), contentType, authorization);
         return ResponseEntity.ok(responseObject);
     }
 
     /**
-     * Create resource data in datastore pass-through running for given cm-handle.
+     * Create resource data for given cm-handle.
      *
-     * @param datastoreName      name of the datastore
-     * @param cmHandle           cm handle identifier
+     * @param datastoreName      name of the datastore (currently only supports "ncmp-datastore:passthrough-running")
+     * @param cmHandleReference  cm handle or alternate identifier
      * @param resourceIdentifier resource identifier
      * @param requestBody        the request body
      * @param contentType        content type of body
@@ -189,23 +186,23 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      */
     @Override
     public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName,
-                                                                     final String cmHandle,
+                                                                     final String cmHandleReference,
                                                                      final String resourceIdentifier,
                                                                      final Object requestBody,
                                                                      final String contentType,
                                                                      final String authorization) {
         validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
-        networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
+        networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandleReference,
                 resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType, authorization);
         return new ResponseEntity<>(HttpStatus.CREATED);
     }
 
     /**
-     * Update resource data in datastore pass-through running for given cm-handle.
+     * Update resource data for given cm-handle.
      *
-     * @param datastoreName      name of the datastore
-     * @param cmHandle           cm handle identifier
+     * @param datastoreName      name of the datastore (currently only supports "ncmp-datastore:passthrough-running")
+     * @param cmHandleReference  cm handle or alternate identifier
      * @param resourceIdentifier resource identifier
      * @param requestBody        the request body
      * @param contentType        content type of the body
@@ -215,23 +212,23 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
 
     @Override
     public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName,
-                                                                       final String cmHandle,
+                                                                       final String cmHandleReference,
                                                                        final String resourceIdentifier,
                                                                        final Object requestBody,
                                                                        final String contentType,
                                                                        final String authorization) {
         validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
-        networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
+        networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandleReference,
                 resourceIdentifier, UPDATE, jsonObjectMapper.asJsonString(requestBody), contentType, authorization);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
     /**
-     * Delete resource data in datastore pass-through running for a given cm-handle.
+     * Delete resource data for a given cm-handle.
      *
-     * @param datastoreName      name of the datastore
-     * @param cmHandle           cm handle identifier
+     * @param datastoreName      name of the datastore (currently only supports "ncmp-datastore:passthrough-running")
+     * @param cmHandleReference  cm handle or alternate identifier
      * @param resourceIdentifier resource identifier
      * @param contentType        content type of the body
      * @param authorization      contents of Authorization header, or null if not present
@@ -239,14 +236,14 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      */
     @Override
     public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName,
-                                                                     final String cmHandle,
+                                                                     final String cmHandleReference,
                                                                      final String resourceIdentifier,
                                                                      final String contentType,
                                                                      final String authorization) {
 
         validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
-        networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
+        networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandleReference,
                 resourceIdentifier, DELETE, NO_BODY, contentType, authorization);
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
@@ -263,11 +260,11 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
             final CmHandleQueryParameters cmHandleQueryParameters) {
         final CmHandleQueryApiParameters cmHandleQueryApiParameters =
                 deprecationHelper.mapOldConditionProperties(cmHandleQueryParameters);
-        final Collection<NcmpServiceCmHandle> cmHandles = networkCmProxyDataService
+        final Collection<NcmpServiceCmHandle> cmHandles = networkCmProxyInventoryFacade
                 .executeCmHandleSearch(cmHandleQueryApiParameters);
-        final List<RestOutputCmHandle> outputCmHandles =
+        final List<RestOutputCmHandle> restOutputCmHandles =
                 cmHandles.stream().map(this::toRestOutputCmHandle).collect(Collectors.toList());
-        return ResponseEntity.ok(outputCmHandles);
+        return ResponseEntity.ok(restOutputCmHandles);
     }
 
     /**
@@ -282,34 +279,35 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         final CmHandleQueryApiParameters cmHandleQueryApiParameters =
                 jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class);
         final Collection<String> cmHandleIds
-            = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters);
+            = networkCmProxyInventoryFacade.executeCmHandleIdSearch(cmHandleQueryApiParameters);
         return ResponseEntity.ok(List.copyOf(cmHandleIds));
     }
 
     /**
      * Search for Cm Handle and Properties by Name.
      *
-     * @param cmHandleId cm-handle identifier
+     * @param cmHandleReference cm-handle or alternate identifier
      * @return cm handle and its properties
      */
     @Override
-    public ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleId) {
-        final NcmpServiceCmHandle ncmpServiceCmHandle = networkCmProxyDataService.getNcmpServiceCmHandle(cmHandleId);
+    public ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleReference) {
+        final NcmpServiceCmHandle ncmpServiceCmHandle
+            = networkCmProxyInventoryFacade.getNcmpServiceCmHandle(cmHandleReference);
         final RestOutputCmHandle restOutputCmHandle = toRestOutputCmHandle(ncmpServiceCmHandle);
         return ResponseEntity.ok(restOutputCmHandle);
     }
 
     /**
-     * Get Cm Handle Properties by Cm Handle Id.
+     * Get Cm Handle Properties by Cm Handle or alternate Identifier.
      *
-     * @param cmHandleId cm-handle identifier
+     * @param cmHandleReference cm-handle or alternate identifier
      * @return cm handle properties
      */
     @Override
     public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId(
-            final String cmHandleId) {
+            final String cmHandleReference) {
         final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties();
-        cmHandlePublicProperties.add(networkCmProxyDataService.getCmHandlePublicProperties(cmHandleId));
+        cmHandlePublicProperties.add(networkCmProxyInventoryFacade.getCmHandlePublicProperties(cmHandleReference));
         final RestOutputCmHandlePublicProperties restOutputCmHandlePublicProperties =
                 new RestOutputCmHandlePublicProperties();
         restOutputCmHandlePublicProperties.setPublicCmHandleProperties(cmHandlePublicProperties);
@@ -319,13 +317,13 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     /**
      * Get Cm Handle State by Cm Handle Id.
      *
-     * @param cmHandleId cm-handle identifier
+     * @param cmHandleReference cm-handle or alternate identifier
      * @return cm handle state
      */
     @Override
     public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(
-            final String cmHandleId) {
-        final CompositeState cmHandleState = networkCmProxyDataService.getCmHandleCompositeState(cmHandleId);
+            final String cmHandleReference) {
+        final CompositeState cmHandleState = networkCmProxyInventoryFacade.getCmHandleCompositeState(cmHandleReference);
         final RestOutputCmHandleCompositeState restOutputCmHandleCompositeState =
                 new RestOutputCmHandleCompositeState();
         restOutputCmHandleCompositeState.setState(
@@ -336,21 +334,23 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     /**
      * Return module definitions.
      *
-     * @param cmHandleId    cm-handle identifier
-     * @param moduleName    module name
-     * @param revision      the revision of the module
+     * @param cmHandleReference   cm handle or alternate id identifier
+     * @param moduleName          module name
+     * @param revision            the revision of the module
      * @return list of module definitions (module name, revision, yang resource content)
      */
     @Override
-    public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitions(final String cmHandleId,
+    public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitions(final String cmHandleReference,
                                                                            final String moduleName,
                                                                            final String revision) {
         final Collection<ModuleDefinition> moduleDefinitions;
         if (StringUtils.hasText(moduleName)) {
             moduleDefinitions =
-                networkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, revision);
+                networkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule(cmHandleReference,
+                    moduleName, revision);
         } else {
-            moduleDefinitions = networkCmProxyDataService.getModuleDefinitionsByCmHandleId(cmHandleId);
+            moduleDefinitions =
+                networkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleReference(cmHandleReference);
             if (StringUtils.hasText(revision)) {
                 log.warn("Ignoring revision filter as no module name is provided");
             }
@@ -365,12 +365,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     /**
      * Return module references for a cm handle.
      *
-     * @param cmHandle the cm handle
+     * @param cmHandleReference cm handle or alternate id identifier
      * @return module references for cm handle. Namespace will be always blank because restConf does not include this.
      */
-    public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) {
+    public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandleReference) {
         final List<RestModuleReference> restModuleReferences =
-                networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream()
+            networkCmProxyInventoryFacade.getYangResourcesModuleReferences(cmHandleReference).stream()
                         .map(ncmpRestInputMapper::toRestModuleReference)
                         .collect(Collectors.toList());
         return new ResponseEntity<>(restModuleReferences, HttpStatus.OK);
@@ -386,22 +386,20 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     @Override
     public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId,
                                                                     final Boolean dataSyncEnabledFlag) {
-        networkCmProxyDataService.setDataSyncEnabled(cmHandleId, dataSyncEnabledFlag);
+        networkCmProxyInventoryFacade.setDataSyncEnabled(cmHandleId, dataSyncEnabledFlag);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-
     private RestOutputCmHandle toRestOutputCmHandle(final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final RestOutputCmHandle restOutputCmHandle = new RestOutputCmHandle();
         final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties();
-        final TrustLevel cmHandleCurrentTrustLevel = trustLevelPerCmHandle.get(ncmpServiceCmHandle.getCmHandleId());
         restOutputCmHandle.setCmHandle(ncmpServiceCmHandle.getCmHandleId());
         cmHandlePublicProperties.add(ncmpServiceCmHandle.getPublicProperties());
         restOutputCmHandle.setPublicCmHandleProperties(cmHandlePublicProperties);
         restOutputCmHandle.setState(cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(
                 ncmpServiceCmHandle.getCompositeState()));
-        if (cmHandleCurrentTrustLevel != null) {
-            restOutputCmHandle.setTrustLevel(cmHandleCurrentTrustLevel.toString());
+        if (ncmpServiceCmHandle.getCurrentTrustLevel() != null) {
+            restOutputCmHandle.setTrustLevel(ncmpServiceCmHandle.getCurrentTrustLevel().toString());
         }
         restOutputCmHandle.setModuleSetTag(ncmpServiceCmHandle.getModuleSetTag());
         restOutputCmHandle.setAlternateId(ncmpServiceCmHandle.getAlternateId());
@@ -417,13 +415,5 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         }
     }
 
-    private NcmpDatastoreRequestHandler getNcmpDatastoreRequestHandler(final String datastoreName) {
-        if (OPERATIONAL.equals(DatastoreType.fromDatastoreName(datastoreName))) {
-            return ncmpCachedResourceRequestHandler;
-        }
-        return ncmpPassthroughResourceRequestHandler;
-    }
-
-
 }
 
index 5467eef..8aa86ad 100755 (executable)
@@ -27,16 +27,17 @@ import java.util.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status;
-import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status;
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse;
 import org.onap.cps.ncmp.rest.api.NetworkCmProxyInventoryApi;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
 import org.onap.cps.ncmp.rest.model.CmHandlerRegistrationErrorResponse;
 import org.onap.cps.ncmp.rest.model.DmiPluginRegistrationErrorResponse;
 import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration;
+import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -47,7 +48,7 @@ import org.springframework.web.bind.annotation.RestController;
 @RequiredArgsConstructor
 public class NetworkCmProxyInventoryController implements NetworkCmProxyInventoryApi {
 
-    private final NetworkCmProxyDataService networkCmProxyDataService;
+    private final NetworkCmProxyInventoryFacade networkCmProxyInventoryFacade;
     private final NcmpRestInputMapper ncmpRestInputMapper;
 
     @Override
@@ -55,8 +56,8 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
         final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = ncmpRestInputMapper
                 .toCmHandleQueryServiceParameters(cmHandleQueryParameters);
 
-        final Collection<String> cmHandleIds = networkCmProxyDataService
-                .executeCmHandleIdSearchForInventory(cmHandleQueryServiceParameters);
+        final Collection<String> cmHandleIds = networkCmProxyInventoryFacade
+                .executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters);
         return ResponseEntity.ok(List.copyOf(cmHandleIds));
     }
 
@@ -69,7 +70,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
     @Override
     public ResponseEntity<List<String>> getAllCmHandleIdsForRegisteredDmi(final String dmiPluginIdentifier) {
         final Collection<String> cmHandleIds =
-                networkCmProxyDataService.getAllCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier);
+            networkCmProxyInventoryFacade.getAllCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier);
         return ResponseEntity.ok(List.copyOf(cmHandleIds));
     }
 
@@ -84,7 +85,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
     public ResponseEntity updateDmiPluginRegistration(
         final @Valid RestDmiPluginRegistration restDmiPluginRegistration) {
         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse =
-            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
+            networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(
                 ncmpRestInputMapper.toDmiPluginRegistration(restDmiPluginRegistration));
         final DmiPluginRegistrationErrorResponse failedRegistrationErrorResponse =
             getFailureRegistrationResponse(dmiPluginRegistrationResponse);
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.exceptions;
+package org.onap.cps.ncmp.rest.controller;
 
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.exception.DmiRequestException;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
-import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException;
-import org.onap.cps.ncmp.api.impl.exception.NcmpException;
-import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException;
-import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController;
-import org.onap.cps.ncmp.rest.controller.NetworkCmProxyInventoryController;
+import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException;
+import org.onap.cps.ncmp.api.data.exceptions.InvalidOperationException;
+import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException;
+import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException;
+import org.onap.cps.ncmp.api.exceptions.DmiRequestException;
+import org.onap.cps.ncmp.api.exceptions.InvalidTopicException;
+import org.onap.cps.ncmp.api.exceptions.NcmpException;
+import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException;
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessage;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiResponse;
 import org.onap.cps.ncmp.rest.model.ErrorMessage;
@@ -69,20 +72,21 @@ public class NetworkCmProxyRestExceptionHandler {
         return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
     }
 
-    @ExceptionHandler({HttpClientRequestException.class})
+    @ExceptionHandler({DmiClientRequestException.class})
     public static ResponseEntity<Object> handleClientRequestExceptions(
-            final HttpClientRequestException httpClientRequestException) {
-        return wrapDmiErrorResponse(httpClientRequestException);
+            final DmiClientRequestException dmiClientRequestException) {
+        return wrapDmiErrorResponse(dmiClientRequestException);
     }
 
-    @ExceptionHandler({DmiRequestException.class, DataValidationException.class, OperationNotSupportedException.class,
-            HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class})
+    @ExceptionHandler({DmiRequestException.class, DataValidationException.class, InvalidOperationException.class,
+        OperationNotSupportedException.class, HttpMessageNotReadableException.class, InvalidTopicException.class,
+        InvalidDatastoreException.class})
     public static ResponseEntity<Object> handleDmiRequestExceptions(final Exception exception) {
         return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
     }
 
-    @ExceptionHandler({AlreadyDefinedException.class})
-    public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) {
+    @ExceptionHandler({AlreadyDefinedException.class, PolicyExecutorException.class})
+    public static ResponseEntity<Object> handleConflictExceptions(final Exception exception) {
         return buildErrorResponse(HttpStatus.CONFLICT, exception);
     }
 
@@ -110,18 +114,16 @@ public class NetworkCmProxyRestExceptionHandler {
         } else {
             errorMessage.setDetails(CHECK_LOGS_FOR_DETAILS);
         }
-        errorMessage.setDetails(
-                exception instanceof CpsException ? ((CpsException) exception).getDetails() : CHECK_LOGS_FOR_DETAILS);
         return new ResponseEntity<>(errorMessage, status);
     }
 
-    private static ResponseEntity<Object> wrapDmiErrorResponse(final HttpClientRequestException
-                                                                     httpClientRequestException) {
+    private static ResponseEntity<Object> wrapDmiErrorResponse(final DmiClientRequestException
+                                                                       dmiClientRequestException) {
         final var dmiErrorMessage = new DmiErrorMessage();
         final var dmiErrorResponse = new DmiErrorMessageDmiResponse();
-        dmiErrorResponse.setHttpCode(httpClientRequestException.getHttpStatus());
-        dmiErrorResponse.setBody(httpClientRequestException.getDetails());
-        dmiErrorMessage.setMessage(httpClientRequestException.getMessage());
+        dmiErrorResponse.setHttpCode(dmiClientRequestException.getHttpStatusCode());
+        dmiErrorResponse.setBody(dmiClientRequestException.getResponseBodyAsString());
+        dmiErrorMessage.setMessage(dmiClientRequestException.getMessage());
         dmiErrorMessage.setDmiResponse(dmiErrorResponse);
         return new ResponseEntity<>(dmiErrorMessage, HttpStatus.BAD_GATEWAY);
     }
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java
deleted file mode 100644 (file)
index e6d6faf..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 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.rest.controller.handlers;
-
-import java.util.function.Supplier;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.NetworkCmProxyQueryService;
-import org.onap.cps.ncmp.api.models.CmResourceAddress;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-import org.onap.cps.spi.FetchDescendantsOption;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
-
-@Component
-public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandler {
-
-    private final NetworkCmProxyDataService networkCmProxyDataService;
-    private final NetworkCmProxyQueryService networkCmProxyQueryService;
-
-    /**
-     * Constructor.
-     *
-     * @param cpsNcmpTaskExecutor        @see org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
-     * @param networkCmProxyDataService  @see org.onap.cps.ncmp.api.NetworkCmProxyDataService
-     * @param networkCmProxyQueryService @see org.onap.cps.ncmp.api.NetworkCmProxyQueryService
-     */
-    public NcmpCachedResourceRequestHandler(final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
-                                            final NetworkCmProxyDataService networkCmProxyDataService,
-                                            final NetworkCmProxyQueryService networkCmProxyQueryService) {
-        super(cpsNcmpTaskExecutor);
-        this.networkCmProxyDataService = networkCmProxyDataService;
-        this.networkCmProxyQueryService = networkCmProxyQueryService;
-    }
-
-    /**
-     * Executes a synchronous query request for given cm handle.
-     * Note. Currently only ncmp-datastore:operational supports query operations.
-     *
-     * @param cmHandleId         the cm handle
-     * @param resourceIdentifier the resource identifier
-     * @param includeDescendants whether include descendants
-     * @return the response entity
-     */
-    public ResponseEntity<Object> executeRequest(final String cmHandleId,
-                                                 final String resourceIdentifier,
-                                                 final boolean includeDescendants) {
-
-        final Supplier<Object> taskSupplier = getTaskSupplierForQueryRequest(cmHandleId, resourceIdentifier,
-            includeDescendants);
-        return executeTaskSync(taskSupplier);
-    }
-
-    @Override
-    protected Supplier<Object> getTaskSupplierForGetRequest(final CmResourceAddress cmResourceAddress,
-                                                  final String optionsParamInQuery,
-                                                  final String topicParamInQuery,
-                                                  final String requestId,
-                                                  final boolean includeDescendants,
-                                                  final String authorization) {
-
-        final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants);
-
-        return () -> networkCmProxyDataService.getResourceDataForCmHandle(cmResourceAddress, fetchDescendantsOption);
-    }
-
-    private Supplier<Object> getTaskSupplierForQueryRequest(final String cmHandleId,
-                                                            final String resourceIdentifier,
-                                                            final boolean includeDescendants) {
-
-        final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants);
-
-        return () -> networkCmProxyQueryService.queryResourceDataOperational(cmHandleId, resourceIdentifier,
-            fetchDescendantsOption);
-    }
-
-    private static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) {
-        return includeDescendants ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-            : FetchDescendantsOption.OMIT_DESCENDANTS;
-    }
-
-
-}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java
deleted file mode 100644 (file)
index 1ae1682..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 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.rest.controller.handlers;
-
-import java.util.Map;
-import java.util.UUID;
-import java.util.function.Supplier;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.models.CmResourceAddress;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-import org.onap.cps.ncmp.rest.util.TopicValidator;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public abstract class NcmpDatastoreRequestHandler {
-
-    private static final String NO_REQUEST_ID = null;
-    private static final String NO_TOPIC = null;
-
-    @Value("${notification.async.executor.time-out-value-in-ms:60000}")
-    protected int timeOutInMilliSeconds;
-
-    @Value("${notification.enabled:true}")
-    protected boolean notificationFeatureEnabled;
-
-    protected final CpsNcmpTaskExecutor cpsNcmpTaskExecutor;
-
-    /**
-     * Executes synchronous/asynchronous get request for given cm handle.
-     *
-     * @param cmResourceAddress   the name of the datastore, cm handle and resource identifier
-     * @param optionsParamInQuery the options param in query
-     * @param topicParamInQuery   the topic param in query
-     * @param includeDescendants  whether include descendants
-     * @param authorization       contents of Authorization header, or null if not present
-     * @return the response entity
-     */
-    public ResponseEntity<Object> executeRequest(final CmResourceAddress cmResourceAddress,
-                                                 final String optionsParamInQuery,
-                                                 final String topicParamInQuery,
-                                                 final boolean includeDescendants,
-                                                 final String authorization) {
-
-        final boolean asyncResponseRequested = topicParamInQuery != null;
-        if (asyncResponseRequested && notificationFeatureEnabled) {
-            return executeAsyncTaskAndGetResponseEntity(cmResourceAddress, optionsParamInQuery, topicParamInQuery,
-                includeDescendants, authorization);
-        }
-
-        if (asyncResponseRequested) {
-            log.warn("Asynchronous request is unavailable as notification feature is currently disabled, "
-                    + "will use synchronous operation.");
-        }
-        final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(cmResourceAddress, optionsParamInQuery,
-            NO_TOPIC, NO_REQUEST_ID, includeDescendants, authorization);
-        return executeTaskSync(taskSupplier);
-    }
-
-
-    private ResponseEntity<Object> executeTaskAsync(final String topicParamInQuery,
-                                                      final String requestId,
-                                                      final Supplier<Object> taskSupplier) {
-        TopicValidator.validateTopicName(topicParamInQuery);
-        log.debug("Received Async request with id {}", requestId);
-        cpsNcmpTaskExecutor.executeTask(taskSupplier, timeOutInMilliSeconds);
-        return ResponseEntity.ok(Map.of("requestId", requestId));
-    }
-
-    protected ResponseEntity<Object> executeTaskSync(final Supplier<Object> taskSupplier) {
-        return ResponseEntity.ok(taskSupplier.get());
-    }
-
-    private ResponseEntity<Object> executeAsyncTaskAndGetResponseEntity(final CmResourceAddress cmResourceAddress,
-                                                                        final String optionsParamInQuery,
-                                                                        final String topicParamInQuery,
-                                                                        final boolean includeDescendants,
-                                                                        final String authorization) {
-        final String requestId = UUID.randomUUID().toString();
-        final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(cmResourceAddress,
-            optionsParamInQuery, topicParamInQuery, requestId, includeDescendants, authorization);
-        return executeTaskAsync(topicParamInQuery, requestId, taskSupplier);
-    }
-
-    protected abstract Supplier<Object> getTaskSupplierForGetRequest(final CmResourceAddress cmResourceAddress,
-                                                  final String optionsParamInQuery,
-                                                  final String topicParamInQuery,
-                                                  final String requestId,
-                                                  final boolean includeDescendant,
-                                                  final String authorization);
-
-}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
deleted file mode 100644 (file)
index 1f87865..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 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.rest.controller.handlers;
-
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL;
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ;
-
-import java.util.Map;
-import java.util.UUID;
-import java.util.function.BiConsumer;
-import java.util.function.Supplier;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
-import org.onap.cps.ncmp.api.impl.operations.OperationType;
-import org.onap.cps.ncmp.api.impl.utils.data.operation.ResourceDataOperationRequestUtils;
-import org.onap.cps.ncmp.api.models.CmResourceAddress;
-import org.onap.cps.ncmp.api.models.DataOperationRequest;
-import org.onap.cps.ncmp.rest.exceptions.OperationNotSupportedException;
-import org.onap.cps.ncmp.rest.exceptions.PayloadTooLargeException;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-import org.onap.cps.ncmp.rest.util.TopicValidator;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
-
-@Component
-public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestHandler {
-
-    private final NetworkCmProxyDataService networkCmProxyDataService;
-
-    private static final Object noReturn = null;
-
-    private static final int MAXIMUM_CM_HANDLES_PER_OPERATION = 50000;
-
-    private static final String PAYLOAD_TOO_LARGE_TEMPLATE = "Operation '%s' affects too many (%d) cm handles";
-
-    /**
-     * Constructor.
-     *
-     * @param cpsNcmpTaskExecutor        @see org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
-     * @param networkCmProxyDataService  @see org.onap.cps.ncmp.api.NetworkCmProxyDataService
-     */
-    public NcmpPassthroughResourceRequestHandler(final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
-                                                 final NetworkCmProxyDataService networkCmProxyDataService) {
-        super(cpsNcmpTaskExecutor);
-        this.networkCmProxyDataService = networkCmProxyDataService;
-    }
-
-    /**
-     * Executes asynchronous request for group of cm handles to resource data.
-     *
-     * @param topicParamInQuery        the topic param in query
-     * @param dataOperationRequest     data operation request details for resource data
-     * @param authorization            contents of Authorization header, or null if not present
-     * @return the response entity
-     */
-    public ResponseEntity<Object> executeRequest(final String topicParamInQuery,
-                                                 final DataOperationRequest dataOperationRequest,
-                                                 final String authorization) {
-        validateDataOperationRequest(topicParamInQuery, dataOperationRequest);
-        if (!notificationFeatureEnabled) {
-            return ResponseEntity.ok(Map.of("status",
-                "Asynchronous request is unavailable as notification feature is currently disabled."));
-        }
-        return getRequestIdAndSendDataOperationRequestToDmiService(topicParamInQuery, dataOperationRequest,
-                authorization);
-    }
-
-    @Override
-    protected Supplier<Object> getTaskSupplierForGetRequest(final CmResourceAddress cmResourceAddress,
-                                                            final String optionsParamInQuery,
-                                                            final String topicParamInQuery,
-                                                            final String requestId,
-                                                            final boolean includeDescendants,
-                                                            final String authorization) {
-
-        return () -> networkCmProxyDataService.getResourceDataForCmHandle(cmResourceAddress, optionsParamInQuery,
-            topicParamInQuery, requestId, authorization);
-    }
-
-    private ResponseEntity<Object> getRequestIdAndSendDataOperationRequestToDmiService(
-            final String topicParamInQuery,
-            final DataOperationRequest dataOperationRequest,
-            final String authorization) {
-        final String requestId = UUID.randomUUID().toString();
-        cpsNcmpTaskExecutor.executeTaskWithErrorHandling(
-            getTaskSupplierForDataOperationRequest(topicParamInQuery, dataOperationRequest, requestId, authorization),
-            getTaskCompletionHandlerForDataOperationRequest(topicParamInQuery, dataOperationRequest, requestId),
-            timeOutInMilliSeconds);
-        return ResponseEntity.ok(Map.of("requestId", requestId));
-    }
-
-    private void validateDataOperationRequest(final String topicParamInQuery,
-                                              final DataOperationRequest dataOperationRequest) {
-        TopicValidator.validateTopicName(topicParamInQuery);
-        dataOperationRequest.getDataOperationDefinitions().forEach(dataOperationDetail -> {
-            if (OperationType.fromOperationName(dataOperationDetail.getOperation()) != READ) {
-                throw new OperationNotSupportedException(
-                    dataOperationDetail.getOperation() + " operation not yet supported");
-            }
-            if (DatastoreType.fromDatastoreName(dataOperationDetail.getDatastore()) == OPERATIONAL) {
-                throw new InvalidDatastoreException(dataOperationDetail.getDatastore()
-                    + " datastore is not supported");
-            }
-            if (dataOperationDetail.getCmHandleIds().size() > MAXIMUM_CM_HANDLES_PER_OPERATION) {
-                final String errorMessage = String.format(PAYLOAD_TOO_LARGE_TEMPLATE,
-                    dataOperationDetail.getOperationId(),
-                    dataOperationDetail.getCmHandleIds().size());
-                throw new PayloadTooLargeException(errorMessage);
-            }
-        });
-    }
-
-    private Supplier<Object> getTaskSupplierForDataOperationRequest(final String topicParamInQuery,
-                                                                    final DataOperationRequest dataOperationRequest,
-                                                                    final String requestId,
-                                                                    final String authorization) {
-        return () -> {
-            networkCmProxyDataService.executeDataOperationForCmHandles(topicParamInQuery,
-                dataOperationRequest,
-                requestId,
-                authorization);
-            return noReturn;
-        };
-    }
-
-    private static BiConsumer<Object, Throwable> getTaskCompletionHandlerForDataOperationRequest(
-            final String topicParamInQuery,
-            final DataOperationRequest dataOperationRequest,
-            final String requestId) {
-        return (result, throwable) ->
-                ResourceDataOperationRequestUtils.handleAsyncTaskCompletionForDataOperationsRequest(topicParamInQuery,
-                        requestId, dataOperationRequest, throwable);
-    }
-
-}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutor.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutor.java
deleted file mode 100644 (file)
index 2601c7a..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 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.rest.executor;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.function.BiConsumer;
-import java.util.function.Supplier;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-@Slf4j
-@Service
-public class CpsNcmpTaskExecutor {
-
-    /**
-     * Execute a task asynchronously, and invoke completion handler when done.
-     *
-     * @param taskSupplier functional method is get() task needed to be executed asynchronously
-     * @param taskCompletionHandler the action to perform on task completion or error
-     * @param timeOutInMillis the time-out value in milliseconds
-     */
-    public void executeTaskWithErrorHandling(final Supplier<Object> taskSupplier,
-                                             final BiConsumer<Object, Throwable> taskCompletionHandler,
-                                             final long timeOutInMillis) {
-        CompletableFuture.supplyAsync(taskSupplier)
-                .orTimeout(timeOutInMillis, MILLISECONDS)
-                .whenCompleteAsync(taskCompletionHandler);
-    }
-
-    /**
-     * Execute a task asynchronously.
-     *
-     * @param taskSupplier functional method is get() task needed to be executed asynchronously
-     * @param timeOutInMillis the time-out value in milliseconds
-     */
-    public void executeTask(final Supplier<Object> taskSupplier, final long timeOutInMillis) {
-        executeTaskWithErrorHandling(taskSupplier, (taskResult, throwable) -> handleTaskCompletion(throwable),
-                timeOutInMillis);
-    }
-
-    private void handleTaskCompletion(final Throwable throwable) {
-        if (throwable == null) {
-            log.info("Async task completed successfully.");
-        } else {
-            log.error("Async task failed. caused by : {}", throwable.toString());
-        }
-    }
-}
-
-
-
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.mapper;
+package org.onap.cps.ncmp.rest.util;
 
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING;
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.LOCKED_MISBEHAVING;
 
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Named;
 import org.mapstruct.NullValueCheckStrategy;
 import org.mapstruct.NullValuePropertyMappingStrategy;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
 import org.onap.cps.ncmp.rest.model.CmHandleCompositeState;
 import org.onap.cps.ncmp.rest.model.DataStores;
 import org.onap.cps.ncmp.rest.model.LockReason;
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.mapper;
+package org.onap.cps.ncmp.rest.util;
 
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.NullValueCheckStrategy;
 import org.mapstruct.NullValuePropertyMappingStrategy;
-import org.onap.cps.ncmp.api.models.DataOperationDefinition;
-import org.onap.cps.ncmp.api.models.DataOperationRequest;
+import org.onap.cps.ncmp.api.data.models.DataOperationDefinition;
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest;
 
 @Mapper(componentModel = "spring", nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
         nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
index 573491c..7492c1f 100644 (file)
@@ -23,8 +23,8 @@ package org.onap.cps.ncmp.rest.util;
 import java.util.ArrayList;
 import java.util.Collections;
 import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
-import org.onap.cps.ncmp.api.models.ConditionApiProperties;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters;
+import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Component;
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.controller;
+package org.onap.cps.ncmp.rest.util;
 
 import org.mapstruct.InheritConfiguration;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.NullValueCheckStrategy;
 import org.mapstruct.NullValuePropertyMappingStrategy;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
 import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration;
 import org.onap.cps.ncmp.rest.model.RestInputCmHandle;
index 2d7e9b2..43403fa 100644 (file)
@@ -28,27 +28,25 @@ import ch.qos.logback.classic.Logger
 import ch.qos.logback.classic.spi.ILoggingEvent
 import ch.qos.logback.core.read.ListAppender
 import com.fasterxml.jackson.databind.ObjectMapper
+import groovy.json.JsonSlurper
 import org.mapstruct.factory.Mappers
 import org.onap.cps.TestUtils
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.ncmp.api.models.CmResourceAddress
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
-import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
-import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
+import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
 import org.onap.cps.ncmp.rest.model.DataOperationDefinition
 import org.onap.cps.ncmp.rest.model.DataOperationRequest
+import org.onap.cps.ncmp.rest.util.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
-import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper
 import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
@@ -59,7 +57,9 @@ import org.springframework.beans.factory.annotation.Value
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
 import org.springframework.test.web.servlet.MockMvc
+import reactor.core.publisher.Mono
 import spock.lang.Shared
 import spock.lang.Specification
 
@@ -67,17 +67,14 @@ import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
 
-import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
-import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
+import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
+import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
+import static org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores
+import static org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
@@ -91,10 +88,13 @@ class NetworkCmProxyControllerSpec extends Specification {
     MockMvc mvc
 
     @SpringBean
-    NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
+    NetworkCmProxyFacade mockNetworkCmProxyFacade = Mock()
 
     @SpringBean
-    NetworkCmProxyQueryService mockNetworkCmProxyQueryService = Mock()
+    NetworkCmProxyInventoryFacade mockNetworkCmProxyInventoryFacade = Mock()
+
+    @SpringBean
+    AlternateIdMatcher mockAlternateIdMatcher = Mock()
 
     @SpringBean
     ObjectMapper objectMapper = new ObjectMapper()
@@ -111,21 +111,9 @@ class NetworkCmProxyControllerSpec extends Specification {
     @SpringBean
     DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
 
-    @SpringBean
-    Map<String, TrustLevel> trustLevelPerCmHandle = [:]
-
-    @SpringBean
-    CpsNcmpTaskExecutor mockCpsTaskExecutor = Mock()
-
     @SpringBean
     DeprecationHelper stubbedDeprecationHelper = Stub()
 
-    @SpringBean
-    NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
-
-    @SpringBean
-    NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService)
-
     @Value('${rest.api.ncmp-base-path}/v1')
     def ncmpBasePathV1
 
@@ -135,17 +123,12 @@ class NetworkCmProxyControllerSpec extends Specification {
 
     @Shared
     def NO_TOPIC = null
-    def NO_REQUEST_ID = null
+    def NO_OPTIONS = null
     def NO_AUTH_HEADER = null
-    def TIMEOUT_FOR_TEST = 1234
 
     def logger = Spy(ListAppender<ILoggingEvent>)
 
     def setup() {
-        ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
-        ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMEOUT_FOR_TEST
-        ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
-        ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMEOUT_FOR_TEST
         setupLogger()
     }
 
@@ -156,12 +139,10 @@ class NetworkCmProxyControllerSpec extends Specification {
     def 'Get Resource Data from pass-through operational.'() {
         given: 'resource data url'
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child&options=(a=1,b=2)"
-        and: 'the expected cm resource address'
-            def expectedCmResourceAddress = new CmResourceAddress(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child')
         when: 'get data resource request is performed'
             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
         then: 'the NCMP data service is called with correct parameters'
-            1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress, '(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
+            1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, '(a=1,b=2)', NO_TOPIC, false, NO_AUTH_HEADER) >> Mono.just(new ResponseEntity<Object>(HttpStatus.OK))
         and: 'response status is Ok'
             assert response.status == HttpStatus.OK.value()
     }
@@ -170,21 +151,19 @@ class NetworkCmProxyControllerSpec extends Specification {
         given: 'resource data url'
             def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational?resourceIdentifier=parent/child${additionalUrlParam}"
         and: 'the expected cm resource address'
-            def expectedCmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'h123', 'parent/child')
         when: 'get data resource request is performed'
             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
         then: 'the NCMP data service is called with correct parameters'
-            1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress, expectedIncludeDescendants)
+            1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, NO_OPTIONS, NO_TOPIC, expectedIncludeDescendants, NO_AUTH_HEADER)
         and: 'response status is OK'
             assert response.status == HttpStatus.OK.value()
         where: 'the following parameters are used'
             scenario                    | additionalUrlParam           || expectedIncludeDescendants
-            'no additional param'       | ''                           || OMIT_DESCENDANTS
-            'include descendants true'  | '&include-descendants=true'  || INCLUDE_ALL_DESCENDANTS
-            'include descendants TRUE'  | '&include-descendants=true'  || INCLUDE_ALL_DESCENDANTS
-            'include descendants false' | '&include-descendants=false' || OMIT_DESCENDANTS
-            'include descendants FALSE' | '&include-descendants=FALSE' || OMIT_DESCENDANTS
-            'options (ignored)'         | '&options=(a-=1)'            || OMIT_DESCENDANTS
+            'no additional param'       | ''                           || false
+            'include descendants true'  | '&include-descendants=true'  || true
+            'include descendants TRUE'  | '&include-descendants=true'  || true
+            'include descendants false' | '&include-descendants=false' || false
+            'include descendants FALSE' | '&include-descendants=FALSE' || false
     }
 
     def 'Execute (async) data operation to read data from dmi service.'() {
@@ -195,45 +174,19 @@ class NetworkCmProxyControllerSpec extends Specification {
             def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)).andReturn().response
         then: 'response status is Ok'
             assert response.status == HttpStatus.OK.value()
-        and: 'async request id is generated'
-            assert response.contentAsString.contains('requestId')
-        then: 'the request is handled asynchronously'
-            1 * mockCpsTaskExecutor.executeTaskWithErrorHandling(*_)
+        then: 'the request for (async) data operation invoked once'
+            1 * mockNetworkCmProxyFacade.executeDataOperationForCmHandles('my-topic-name', _, NO_AUTH_HEADER)
         where: 'the following data stores are used'
             datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
     }
 
-    def 'Execute (async) data operation with some validation error.'() {
-        given: 'data operation url'
-            def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
-            def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest('read', 'invalid datastore'))
-        when: 'post data resource request is performed'
-            def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)).andReturn().response
-        then: 'response status is BAD_REQUEST'
-            assert response.status == HttpStatus.BAD_REQUEST.value()
-    }
-
-    def 'Get data operation resource data when notification feature is disabled for datastore: #datastore.'() {
-        given: 'data operation url'
-            def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
-            def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
-            ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
-        when: 'post data resource request is performed'
-            def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)
-            ).andReturn().response
-        then: 'response status is Ok'
-            assert response.status == HttpStatus.OK.value()
-        and: 'async request id is unavailable'
-            assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
-    }
-
     def 'Query Resource Data from operational.'() {
         given: 'the query resource data url'
-            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query?cps-path=/cps/path"
+            def getUrl = "$ncmpBasePathV1/ch/ch-1/data/ds/ncmp-datastore:operational/query?cps-path=/cps/path"
         when: 'the query data resource request is performed'
             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
         then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
-            1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle','/cps/path',FetchDescendantsOption.OMIT_DESCENDANTS)
+            1 * mockNetworkCmProxyFacade.queryResourceDataForCmHandle('ch-1','/cps/path', false)
         and: 'response status is Ok'
             assert response.status == HttpStatus.OK.value()
     }
@@ -251,16 +204,17 @@ class NetworkCmProxyControllerSpec extends Specification {
 
     def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
         given: 'resource data url'
-            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=$resourceIdentifier&options=(a=1,b=2)"
+            def getUrl = "$ncmpBasePathV1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=$resourceIdentifier&options=(a=1)"
         and: 'ncmp service returns json object'
-            def expectedCmResourceAddress = new CmResourceAddress(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', resourceIdentifier)
-            mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >> '{valid-json}'
+            1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, '(a=1)', NO_TOPIC, false, NO_AUTH_HEADER)
+                    >> new ResponseEntity<Object>('{valid-json}', HttpStatus.OK)
         when: 'get data resource request is performed'
             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
         then: 'response status is Ok'
-            response.status == HttpStatus.OK.value()
-        and: 'response contains valid object body'
-            response.getContentAsString() == '{valid-json}'
+            assert response.status == 200
+        and: 'response contains the object returned by the service'
+            def responseAsJsonObject = new JsonSlurper().parseText(response.getContentAsString())
+            assert responseAsJsonObject.body == '{valid-json}'
         where: 'tokens are used in the resource identifier parameter'
             scenario                       | resourceIdentifier
             '/'                            | 'id/with/slashes'
@@ -277,7 +231,7 @@ class NetworkCmProxyControllerSpec extends Specification {
         when: 'update data resource request is performed'
             def response = mvc.perform(put(updateUrl).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
         then: 'ncmp service method to update resource is called'
-            1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle','parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+            1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle','parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
         and: 'the response status is OK'
             assert response.status == HttpStatus.OK.value()
     }
@@ -288,7 +242,7 @@ class NetworkCmProxyControllerSpec extends Specification {
         when: 'create resource request is performed'
             def response = mvc.perform(post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
         then: 'ncmp service method to create resource called'
-            1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+            1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
         and: 'resource is created'
             assert response.status == HttpStatus.CREATED.value()
     }
@@ -299,7 +253,7 @@ class NetworkCmProxyControllerSpec extends Specification {
         when: 'get module resource request is performed'
             def response = mvc.perform(get(getUrl)).andReturn().response
         then: 'ncmp service method to get yang resource module references is called'
-            mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle') >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
+            mockNetworkCmProxyInventoryFacade.getYangResourcesModuleReferences('some-cmhandle') >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
         and: 'response contains an array with the module name and revision'
             response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
         and: 'response returns an OK http code'
@@ -314,15 +268,14 @@ class NetworkCmProxyControllerSpec extends Specification {
             def cmHandle1 = new NcmpServiceCmHandle()
             cmHandle1.cmHandleId = 'ch-1'
             cmHandle1.publicProperties = [color: 'yellow']
+            cmHandle1.currentTrustLevel = TrustLevel.NONE
             def cmHandle2 = new NcmpServiceCmHandle()
             cmHandle2.cmHandleId = 'ch-2'
             cmHandle2.publicProperties = [color: 'green']
             cmHandle2.alternateId = 'someAlternateId'
             cmHandle2.moduleSetTag = 'someModuleSetTag'
             cmHandle2.dataProducerIdentifier = 'someDataProducerIdentifier'
-            mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
-        and: 'map for trust level per cmHandle has value for only one cm handle'
-              trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE)
+            mockNetworkCmProxyInventoryFacade.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
         when: 'the searches api is invoked'
             def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString)).andReturn().response
         then: 'response status returns OK'
@@ -331,19 +284,18 @@ class NetworkCmProxyControllerSpec extends Specification {
             assert response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"NONE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":null,"moduleSetTag":"someModuleSetTag","alternateId":"someAlternateId","dataProducerIdentifier":"someDataProducerIdentifier"}]'
     }
 
-    def 'Get complete Cm Handle details by Cm Handle id.'() {
-        given: 'an endpoint and a cm handle'
-            def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
+    def 'Get complete Cm Handle details by Cm Handle Reference.'() {
+        given: 'an endpoint and a cm handle reference'
+            def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference"
         and: 'an existing ncmp service cm handle'
             def cmHandleId = 'some-cm-handle'
+            def alternateId = 'some-alternate-id'
             def dmiProperties = [prop: 'some DMI property']
             def publicProperties = ["public prop": 'some public property']
             def compositeState = compositeStateTestObject()
-            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
-        and: 'the service method is invoked with the cm handle id'
-            1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
-        and: 'map for trust level per cmHandle has values'
-            trustLevelPerCmHandle.get('some-cm-handle') >> { TrustLevel.COMPLETE }
+            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: alternateId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, currentTrustLevel: TrustLevel.COMPLETE)
+        and: 'the service method is invoked with the cm handle reference'
+            1 * mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('some-cm-handle-reference') >> ncmpServiceCmHandle
         when: 'the cm handle details api is invoked'
             def response = mvc.perform(
                     get(cmHandleDetailsEndpoint)).andReturn().response
@@ -357,13 +309,13 @@ class NetworkCmProxyControllerSpec extends Specification {
             assert !response.contentAsString.contains("some DMI property")
     }
 
-    def 'Get Cm Handle public properties by Cm Handle id.'() {
+    def 'Get Cm Handle public properties by Cm Handle Reference.'() {
         given: 'a cm handle properties endpoint'
-            def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
+            def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/properties"
         and: 'some cm handle public properties'
             def publicProperties = ['public prop': 'some public property']
         and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
-            1 * mockNetworkCmProxyDataService.getCmHandlePublicProperties('some-cm-handle') >> publicProperties
+            1 * mockNetworkCmProxyInventoryFacade.getCmHandlePublicProperties('some-cm-handle-reference') >> publicProperties
         when: 'the cm handle properties api is invoked'
             def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
         then: 'the correct response is returned'
@@ -372,13 +324,13 @@ class NetworkCmProxyControllerSpec extends Specification {
             assertContainsPublicProperties(response)
     }
 
-    def 'Get Cm Handle composite state by Cm Handle id.'() {
+    def 'Get Cm Handle composite state by Cm Handle Reference.'() {
         given: 'a cm handle state endpoint'
-            def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
+            def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/state"
         and: 'some cm handle composite state'
             def compositeState = compositeStateTestObject()
         and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
-            1 * mockNetworkCmProxyDataService.getCmHandleCompositeState('some-cm-handle') >> compositeState
+            1 * mockNetworkCmProxyInventoryFacade.getCmHandleCompositeState('some-cm-handle-reference') >> compositeState
         when: 'the cm handle state api is invoked'
             def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
         then: 'the correct response is returned'
@@ -392,16 +344,15 @@ class NetworkCmProxyControllerSpec extends Specification {
             def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
             String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
         and: 'the service method is invoked with module names and returns two cm handles'
-            def cmHandel1 = new NcmpServiceCmHandle()
-            cmHandel1.cmHandleId = 'ch-1'
-            cmHandel1.publicProperties = [color: 'yellow']
-            def cmHandel2 = new NcmpServiceCmHandle()
-            cmHandel2.cmHandleId = 'ch-2'
-            cmHandel2.publicProperties = [color: 'green']
-            mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
-        and: 'map for trust level per cmHandle has values'
-            trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
-            trustLevelPerCmHandle.put('ch-2', TrustLevel.NONE)
+            def cmHandle1 = new NcmpServiceCmHandle()
+            cmHandle1.cmHandleId = 'ch-1'
+            cmHandle1.publicProperties = [color: 'yellow']
+            cmHandle1.currentTrustLevel = TrustLevel.COMPLETE
+            def cmHandle2 = new NcmpServiceCmHandle()
+            cmHandle2.cmHandleId = 'ch-2'
+            cmHandle2.publicProperties = [color: 'green']
+            cmHandle2.currentTrustLevel = TrustLevel.NONE
+            mockNetworkCmProxyInventoryFacade.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
         when: 'the searches api is invoked'
             def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString)).andReturn().response
         then: 'an empty cm handle identifier is returned'
@@ -412,7 +363,7 @@ class NetworkCmProxyControllerSpec extends Specification {
         given: 'an endpoint and json data'
             def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
         and: 'the service method is invoked with module names and returns cm handle ids'
-            1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['ch-1', 'ch-2']
+            1 * mockNetworkCmProxyInventoryFacade.executeCmHandleIdSearch(_) >> ['ch-1', 'ch-2']
         when: 'the searches api is invoked'
             def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content('{}')).andReturn().response
         then: 'cm handle ids are returned'
@@ -434,7 +385,7 @@ class NetworkCmProxyControllerSpec extends Specification {
         when: 'patch data resource request is performed'
             def response = mvc.perform(patch(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content(requestBody)).andReturn().response
         then: 'ncmp service method to update resource is called'
-            1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+            1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
         and: 'the response status is OK'
             assert response.status == HttpStatus.OK.value()
     }
@@ -445,29 +396,16 @@ class NetworkCmProxyControllerSpec extends Specification {
         when: 'delete data resource request is performed'
             def response = mvc.perform(delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
         then: 'the ncmp service method to delete resource is called (with null as body)'
-            1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+            1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
         and: 'the response is No Content'
             assert response.status == HttpStatus.NO_CONTENT.value()
     }
 
-    def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
-        given: 'resource data url'
-            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
-        when: 'get data resource request is performed'
-            def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON_VALUE)).andReturn().response
-        then: 'async request id is generated'
-            assert response.contentAsString.contains("requestId")
-        where: 'the following parameters are used'
-            scenario                   | datastoreInUrl
-            ':passthrough-operational' | 'passthrough-operational'
-            ':passthrough-running'     | 'passthrough-running'
-    }
-
     def 'Getting module definitions for a module'() {
         when: 'get module definition request is performed with module name'
             def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=sampleModuleName")).andReturn().response
         then: 'ncmp service method is invoked with correct parameters'
-            mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', 'sampleModuleName', _)
+            mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', 'sampleModuleName', _)
                 >> [new ModuleDefinition('sampleModuleName', '2021-10-03','module sampleModuleName{ sample module content }')]
         and: 'response contains an array with the module name, revision and content'
             response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
@@ -478,12 +416,12 @@ class NetworkCmProxyControllerSpec extends Specification {
     def 'Getting module definitions filtering on #scenario'() {
         when: 'get module definition request is performed'
             def response = mvc.perform(
-                get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=" + moduleName + "&revision=" + revision))
+                get("$ncmpBasePathV1/ch/some-cmhandle-reference/modules/definitions?module-name=" + moduleName + "&revision=" + revision))
                 .andReturn().response
-        then: 'ncmp service method to get definitions by cm handle is invoked when needed'
-            numberOfCallsToByCmHandleId * mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle') >> []
+        then: 'ncmp service method to get definitions by cm handle reference is invoked when needed'
+            numberOfCallsToByCmHandleId * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleReference('some-cmhandle-reference') >> []
         and: 'ncmp service method to get definitions by module is invoked when needed'
-            numberOfCallsToByModule * mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', moduleName, revision) >> []
+            numberOfCallsToByModule * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle-reference', moduleName, revision) >> []
         and: 'response returns an OK http code'
             response.status == HttpStatus.OK.value()
         and: 'the correct message is logged when needed'
@@ -506,7 +444,7 @@ class NetworkCmProxyControllerSpec extends Specification {
                     put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
                     .andReturn().response
         then: 'method to set data sync enabled is called'
-            1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
+            1 * mockNetworkCmProxyInventoryFacade.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
         and: 'the response returns an OK http code'
             response.status == HttpStatus.OK.value()
         where: 'the following parameters are used'
@@ -515,23 +453,6 @@ class NetworkCmProxyControllerSpec extends Specification {
             'disabled' | false
     }
 
-    def 'Get Resource Data from operational with or without descendants'() {
-        given: 'resource data url with descendants #enabled'
-            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
-        and: 'the expected target'
-            def expectedCmResourceAddress = new CmResourceAddress(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child')
-        when: 'get data resource request is performed'
-            def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
-        then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
-            1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress, descendantsOption)
-        and: 'response status is Ok'
-            assert response.status == HttpStatus.OK.value()
-        where: 'the following parameters are used'
-            booleanValue | descendantsOption
-            false        | OMIT_DESCENDANTS
-            true         | INCLUDE_ALL_DESCENDANTS
-    }
-
     def 'Attempt execute #operation rest operation on resource data with #scenario'() {
         given: 'resource data url'
             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
index 7b850a7..97c68f0 100644 (file)
@@ -23,15 +23,16 @@ package org.onap.cps.ncmp.rest.controller
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.TestUtils
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters
 import org.onap.cps.ncmp.rest.model.CmHandlerRegistrationErrorResponse
 import org.onap.cps.ncmp.rest.model.DmiPluginRegistrationErrorResponse
 import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
+import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
@@ -54,7 +55,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
     MockMvc mvc
 
     @SpringBean
-    NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
+    NetworkCmProxyInventoryFacade mockNetworkCmProxyInventoryFacade = Mock()
 
     @SpringBean
     NcmpRestInputMapper ncmpRestInputMapper = Mock()
@@ -83,7 +84,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
                     .content(jsonData)
             ).andReturn().response
         then: 'the converted object is forwarded to the registration service'
-            1 * mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(mockDmiPluginRegistration) >> new DmiPluginRegistrationResponse()
+            1 * mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(mockDmiPluginRegistration) >> new DmiPluginRegistrationResponse()
         and: 'response status is no content'
             response.status == HttpStatus.OK.value()
         where: 'the following registration json is used'
@@ -112,7 +113,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
         and: 'the mapper service returns a converted object'
             ncmpRestInputMapper.toCmHandleQueryServiceParameters(_) >> cmHandleQueryServiceParameters
         and: 'the service returns the desired results'
-            mockNetworkCmProxyDataService.executeCmHandleIdSearchForInventory(cmHandleQueryServiceParameters) >> serviceMockResponse
+            mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) >> serviceMockResponse
         when: 'post request is performed & search is called with the given request parameters'
             def response = mvc.perform(
                     post("$ncmpBasePathV1/ch/searches")
@@ -135,7 +136,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
         and: 'the mapper service returns a converted object'
             ncmpRestInputMapper.toCmHandleQueryServiceParameters(_) >> cmHandleQueryServiceParameters
         and: 'the service returns the desired results'
-            mockNetworkCmProxyDataService.executeCmHandleIdSearchForInventory(cmHandleQueryServiceParameters) >> serviceMockResponse
+            mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) >> serviceMockResponse
         when: 'post request is performed & search is called with the given request parameters'
             def response = mvc.perform(
                     post("$ncmpBasePathV1/ch/searches")
@@ -156,7 +157,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
         given: 'the mapper service returns a converted object'
             ncmpRestInputMapper.toCmHandleQueryServiceParameters(_) >> cmHandleQueryServiceParameters
         and: 'the service returns the desired results'
-            mockNetworkCmProxyDataService.executeCmHandleIdSearchForInventory(cmHandleQueryServiceParameters) >> []
+            mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) >> []
         when: 'post request is performed & search is called with the given request parameters'
             def response = mvc.perform(
                     post("$ncmpBasePathV1/ch/searches")
@@ -180,7 +181,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
                 updatedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-2')],
                 removedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-3')]
             )
-            mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse
+            mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse
         when: 'registration endpoint is invoked'
             def response = mvc.perform(
                 post("$ncmpBasePathV1/ch")
@@ -204,7 +205,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
                 removedCmHandles: [removeCmHandleResponse],
                 upgradedCmHandles: [upgradeCmHandleResponse]
             )
-            mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse
+            mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse
         when: 'registration endpoint is invoked'
             def response = mvc.perform(
                 post("$ncmpBasePathV1/ch")
@@ -237,7 +238,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
         given: 'an endpoint for returning cm handle IDs for a registered dmi plugin'
             def getUrl = "$ncmpBasePathV1/ch/cmHandles?dmi-plugin-identifier=some-dmi-plugin-identifier"
         and: 'a collection of cm handle IDs are returned'
-            1 * mockNetworkCmProxyDataService.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
+            1 * mockNetworkCmProxyInventoryFacade.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
                     >> ['cm-handle-id-1','cm-handle-id-2']
         when: 'the endpoint is invoked'
             def response = mvc.perform(
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.exceptions
-
-import static org.springframework.http.HttpStatus.BAD_GATEWAY
-import static org.springframework.http.HttpStatus.BAD_REQUEST
-import static org.springframework.http.HttpStatus.CONFLICT
-import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
-import static org.springframework.http.HttpStatus.NOT_FOUND
-import static org.springframework.http.HttpStatus.PAYLOAD_TOO_LARGE
-import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMP
-import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+package org.onap.cps.ncmp.rest.controller
 
 import groovy.json.JsonSlurper
 import org.mapstruct.factory.Mappers
 import org.onap.cps.TestUtils
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
-import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException
-import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
-import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
-import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
+import org.onap.cps.ncmp.api.data.exceptions.InvalidOperationException
+import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException
+import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException
+import org.onap.cps.ncmp.api.exceptions.DmiRequestException
+import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler
+import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler
+import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
+import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.rest.util.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
+import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
@@ -60,6 +55,18 @@ import org.springframework.test.web.servlet.MockMvc
 import spock.lang.Shared
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
+import static org.onap.cps.ncmp.rest.controller.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMP
+import static org.onap.cps.ncmp.rest.controller.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY
+import static org.springframework.http.HttpStatus.BAD_GATEWAY
+import static org.springframework.http.HttpStatus.BAD_REQUEST
+import static org.springframework.http.HttpStatus.CONFLICT
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
+import static org.springframework.http.HttpStatus.NOT_FOUND
+import static org.springframework.http.HttpStatus.PAYLOAD_TOO_LARGE
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+
 @WebMvcTest
 class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
 
@@ -67,7 +74,13 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
     MockMvc mvc
 
     @SpringBean
-    NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
+    NetworkCmProxyFacade mockNetworkCmProxyFacade = Mock()
+
+    @SpringBean
+    NetworkCmProxyInventoryFacade mockNetworkCmProxyInventoryFacade = Mock()
+
+    @SpringBean
+    InventoryPersistence mockInventoryPersistence = Mock()
 
     @SpringBean
     JsonObjectMapper stubbedJsonObjectMapper = Stub()
@@ -81,9 +94,6 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
     @SpringBean
     DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
 
-    @SpringBean
-    CpsNcmpTaskExecutor stubbedCpsTaskExecutor = Stub()
-
     @SpringBean
     DeprecationHelper stubbedDeprecationHelper = Stub()
 
@@ -112,23 +122,26 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
         dataNodeBaseEndpointNcmpInventory = "$basePathNcmpInventory/v1"
     }
 
-    def 'Get request with #scenario exception returns correct HTTP Status with #scenario'() {
+    def 'Get request with #scenario exception returns correct HTTP Status with #scenario exception'() {
         when: 'an exception is thrown by the service'
             setupTestException(exception, NCMP)
             def response = performTestRequest(NCMP)
         then: 'an HTTP response is returned with correct message and details'
             assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails)
         where:
-            scenario              | exception                                                        || expectedErrorCode     | expectedErrorMessage        | expectedErrorDetails
-            'CPS'                 | new CpsException(sampleErrorMessage, sampleErrorDetails)         || INTERNAL_SERVER_ERROR | sampleErrorMessage          | sampleErrorDetails
-            'NCMP-server'         | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails)  || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
-            'NCMP-client'         | new DmiRequestException(sampleErrorMessage, sampleErrorDetails)  || BAD_REQUEST           | sampleErrorMessage          | null
-            'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND             | 'DataNode not found'        | null
-            'other'               | new IllegalStateException(sampleErrorMessage)                    || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
-            'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND             | 'DataNode not found'        | 'DataNode not found'
-            'Existing entry'      | new AlreadyDefinedException('name',null)                         || CONFLICT              | 'Already defined exception' | 'name already exists'
-            'Existing entries'    | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT              | 'Already defined exception' | '2 data node(s) already exist'
-            'Operation too large' | new PayloadTooLargeException(sampleErrorMessage)                 || PAYLOAD_TOO_LARGE     | sampleErrorMessage          | 'Check logs'
+            scenario                | exception                                                           || expectedErrorCode     | expectedErrorMessage        | expectedErrorDetails
+            'CPS'                   | new CpsException(sampleErrorMessage, sampleErrorDetails)            || INTERNAL_SERVER_ERROR | sampleErrorMessage          | sampleErrorDetails
+            'NCMP-server'           | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails)     || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
+            'DMI Request'           | new DmiRequestException(sampleErrorMessage, sampleErrorDetails)     || BAD_REQUEST           | sampleErrorMessage          | null
+            'Invalid Operation'     | new InvalidOperationException('some reason')                        || BAD_REQUEST           | 'some reason'               | null
+            'Unsupported Operation' | new OperationNotSupportedException('not yet')                       || BAD_REQUEST           | 'not yet'                   | null
+            'DataNode Validation'   | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName')    || NOT_FOUND             | 'DataNode not found'        | null
+            'other'                 | new IllegalStateException(sampleErrorMessage)                       || INTERNAL_SERVER_ERROR | sampleErrorMessage          | null
+            'Data Node Not Found'   | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName')    || NOT_FOUND             | 'DataNode not found'        | 'DataNode not found'
+            'Existing entry'        | new AlreadyDefinedException('name',null)                            || CONFLICT              | 'Already defined exception' | 'name already exists'
+            'Existing entries'      | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName')    || CONFLICT              | 'Already defined exception' | '2 data node(s) already exist'
+            'Operation too large'   | new PayloadTooLargeException(sampleErrorMessage)                    || PAYLOAD_TOO_LARGE     | sampleErrorMessage          | 'Check logs'
+            'Policy Executor'       | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails) || CONFLICT              | sampleErrorMessage          | sampleErrorDetails
     }
 
     def 'Post request with exception returns correct HTTP Status.'() {
@@ -143,7 +156,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
 
     def 'Failing DMI Request - passthrough scenario'() {
         given: 'failing DMI request'
-            setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400), NCMP)
+            setupTestException(new DmiClientRequestException(400, 'Error Message Details NCMP', 'Bad Request from DMI', UNABLE_TO_READ_RESOURCE_DATA), NCMP)
         when: 'the DMI request is executed'
             def response = performTestRequest(NCMP)
         then: 'NCMP service responds with 502 Bad Gateway status'
@@ -155,9 +168,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
 
     def setupTestException(exception, apiType) {
         if (NCMP == apiType) {
-            mockNetworkCmProxyDataService.getYangResourcesModuleReferences(*_) >> { throw exception }
+            mockNetworkCmProxyInventoryFacade.getYangResourcesModuleReferences(*_) >> { throw exception }
         }
-        mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(*_) >> { throw exception }
+        mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> { throw exception }
     }
 
     def performTestRequest(apiType) {
@@ -176,8 +189,5 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
         assert expectedErrorDetails == null || content['details'].toString().contains(expectedErrorDetails)
     }
 
-    enum ApiType {
-        NCMP,
-        NCMPINVENTORY;
-    }
+    enum ApiType { NCMP,  NCMPINVENTORY  }
 }
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutorSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutorSpec.groovy
deleted file mode 100644 (file)
index 4c8c40f..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 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.rest.executor
-
-import ch.qos.logback.classic.Level
-import ch.qos.logback.classic.Logger
-import ch.qos.logback.classic.spi.ILoggingEvent
-import ch.qos.logback.core.read.ListAppender
-import org.slf4j.LoggerFactory
-import spock.lang.Specification
-import spock.util.concurrent.PollingConditions
-
-class CpsNcmpTaskExecutorSpec extends Specification {
-
-    def objectUnderTest = new CpsNcmpTaskExecutor()
-    def logger = Spy(ListAppender<ILoggingEvent>)
-    def enoughTime = 100
-    def notEnoughTime = 10
-
-    void setup() {
-        ((Logger) LoggerFactory.getLogger(CpsNcmpTaskExecutor.class)).addAppender(logger)
-        logger.start()
-    }
-
-    void cleanup() {
-        ((Logger) LoggerFactory.getLogger(CpsNcmpTaskExecutor.class)).detachAndStopAllAppenders()
-    }
-
-    def 'Execute successful task.'() {
-        when: 'task is executed'
-            objectUnderTest.executeTask(taskSupplier(), enoughTime)
-        then: 'an event is logged with level INFO'
-            new PollingConditions().within(1) {
-                def loggingEvent = getLoggingEvent()
-                assert loggingEvent.level == Level.INFO
-            }
-        and: 'the log indicates the task completed successfully'
-            assert loggingEvent.formattedMessage == 'Async task completed successfully.'
-    }
-
-    def 'Execute failing task.'() {
-        when: 'task is executed'
-            objectUnderTest.executeTask(taskSupplierForFailingTask(), enoughTime)
-        then: 'an event is logged with level ERROR'
-            new PollingConditions().within(1) {
-                def loggingEvent = getLoggingEvent()
-                assert loggingEvent.level == Level.ERROR
-            }
-        and: 'the original error message is logged'
-            assert loggingEvent.formattedMessage.contains('original exception message')
-    }
-
-    def 'Task times out.'() {
-        when: 'task is executed without enough time to complete'
-            objectUnderTest.executeTask(taskSupplierForLongRunningTask(), notEnoughTime)
-        then: 'an event is logged with level ERROR'
-            new PollingConditions().within(1) {
-                def loggingEvent = getLoggingEvent()
-                assert loggingEvent.level == Level.ERROR
-            }
-        and: 'a timeout error message is logged'
-            assert loggingEvent.formattedMessage.contains('java.util.concurrent.TimeoutException')
-    }
-
-    def taskSupplier() {
-        return () -> 'hello world'
-    }
-
-    def taskSupplierForFailingTask() {
-        return () -> { throw new RuntimeException('original exception message') }
-    }
-
-    def taskSupplierForLongRunningTask() {
-        return () -> { sleep(enoughTime) }
-    }
-
-    def getLoggingEvent() {
-        return logger.list[0]
-    }
-
-}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.mapper
-
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED
+package org.onap.cps.ncmp.rest.util
 
 import org.mapstruct.factory.Mappers
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
 import org.onap.cps.ncmp.rest.model.CmHandleCompositeState
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
 import spock.lang.Specification
+
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
 
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.LOCKED_MISBEHAVING
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED
+
 class CmHandleStateMapperSpec extends Specification {
 
     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
index 8c212d3..b63cd7a 100644 (file)
@@ -21,8 +21,8 @@
 package org.onap.cps.ncmp.rest.util
 
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters
-import org.onap.cps.ncmp.api.models.ConditionApiProperties
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters
+import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters
 import org.onap.cps.ncmp.rest.model.ConditionProperties
 import org.onap.cps.ncmp.rest.model.ModuleNameAsJsonObject
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.controller
+package org.onap.cps.ncmp.rest.util
 
 import org.mapstruct.factory.Mappers
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters
 import org.onap.cps.ncmp.rest.model.ConditionProperties
 import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration
 import org.onap.cps.ncmp.rest.model.RestInputCmHandle
 import org.onap.cps.ncmp.rest.model.RestModuleDefinition
 import org.onap.cps.ncmp.rest.model.RestModuleReference
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
 import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
 import spock.lang.Specification
index 9df1e58..aa57167 100644 (file)
@@ -26,4 +26,4 @@ notification:
     enabled: true
     async:
         executor:
-            time-out-value-in-ms: 2000
\ No newline at end of file
+            time-out-value-in-ms: 2000
index fc41da3..7871aaf 100644 (file)
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
         <minimum-coverage>0.98</minimum-coverage>
     </properties>
     <dependencies>
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-exporter-otlp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-sdk-extension-jaeger-remote-sampler</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.opentelemetry.instrumentation</groupId>
+            <artifactId>opentelemetry-kafka-clients-2.6</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
             <groupId>${project.groupId}</groupId>
             <artifactId>cps-path-parser</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.hazelcast</groupId>
             <artifactId>hazelcast-spring</artifactId>
@@ -70,8 +90,8 @@
             <artifactId>mapstruct-processor</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-web</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
         </dependency>
         <!-- T E S T - D E P E N D E N C I E S -->
         <dependency>
             <artifactId>spock</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
     </dependencies>
 </project>
index bdc3dee..8cfad7d 100644 (file)
@@ -26,14 +26,12 @@ import lombok.Getter;
 public enum NcmpResponseStatus {
 
     SUCCESS("0", "Successfully applied changes"),
-    SUCCESSFULLY_APPLIED_SUBSCRIPTION("1", "successfully applied subscription"),
+    CM_DATA_SUBSCRIPTION_ACCEPTED("1", "ACCEPTED"),
     CM_HANDLES_NOT_FOUND("100", "cm handle id(s) not found"),
     CM_HANDLES_NOT_READY("101", "cm handle(s) not ready"),
     DMI_SERVICE_NOT_RESPONDING("102", "dmi plugin service is not responding"),
     UNABLE_TO_READ_RESOURCE_DATA("103", "dmi plugin service is not able to read resource data"),
-    PARTIALLY_APPLIED_SUBSCRIPTION("104", "partially applied subscription"),
-    SUBSCRIPTION_NOT_APPLICABLE("105", "subscription not applicable for all cm handles"),
-    SUBSCRIPTION_PENDING("106", "subscription pending for all cm handles"),
+    CM_DATA_SUBSCRIPTION_REJECTED("104", "REJECTED"),
     UNKNOWN_ERROR("108", "Unknown error"),
     CM_HANDLE_ALREADY_EXIST("109", "cm-handle already exists"),
     CM_HANDLE_INVALID_ID("110", "cm-handle has an invalid character(s) in id"),
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
deleted file mode 100644 (file)
index 20545d7..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021 highstreet technologies GmbH
- *  Modifications Copyright (C) 2021-2024 Nordix Foundation
- *  Modifications Copyright (C) 2021 Pantheon.tech
- *  Modifications 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;
-
-import java.util.Collection;
-import java.util.Map;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.operations.OperationType;
-import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
-import org.onap.cps.ncmp.api.models.CmResourceAddress;
-import org.onap.cps.ncmp.api.models.DataOperationRequest;
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
-import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.model.ModuleDefinition;
-import org.onap.cps.spi.model.ModuleReference;
-
-/*
- * Datastore interface for handling CPS data.
- */
-public interface NetworkCmProxyDataService {
-
-    /**
-     * Registration of New CM Handles.
-     *
-     * @param dmiPluginRegistration Dmi Plugin Registration
-     * @return dmiPluginRegistrationResponse
-     */
-    DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration);
-
-    /**
-     * Get resource data for given data store using dmi.
-     *
-     * @param cmResourceAddress   target datastore, cm handle and resource identifier
-     * @param optionsParamInQuery options query
-     * @param topicParamInQuery   topic name for (triggering) async responses
-     * @param requestId           unique requestId for async request
-     * @param authorization       contents of Authorization header, or null if not present
-     * @return {@code Object} resource data
-     */
-    Object getResourceDataForCmHandle(CmResourceAddress cmResourceAddress,
-                                      String optionsParamInQuery,
-                                      String topicParamInQuery,
-                                      String requestId,
-                                      String authorization);
-
-    /**
-     * Get resource data for operational.
-     *
-     * @param cmResourceAddress     target datastore, cm handle and resource identifier
-     * @Link FetchDescendantsOption fetch descendants option
-     * @return {@code Object} resource data
-     */
-    Object getResourceDataForCmHandle(CmResourceAddress cmResourceAddress,
-                                      FetchDescendantsOption fetchDescendantsOption);
-
-    /**
-     * Execute (async) data operation for group of cm handles using dmi.
-     *
-     * @param topicParamInQuery        topic name for (triggering) async responses
-     * @param dataOperationRequest     contains a list of operation definitions(multiple operations)
-     * @param requestId                request ID
-     * @param authorization            contents of Authorization header, or null if not present
-     */
-    void executeDataOperationForCmHandles(String topicParamInQuery,
-                                          DataOperationRequest dataOperationRequest,
-                                          String requestId,
-                                          String authorization);
-
-
-    /**
-     * Write resource data for data store pass-through running using dmi for given cm-handle.
-     *
-     * @param cmHandleId         cm handle identifier
-     * @param resourceIdentifier resource identifier
-     * @param operationType      required operation type
-     * @param requestBody        request body to create resource
-     * @param contentType        content type in body
-     * @param authorization       contents of Authorization header, or null if not present
-     * @return {@code Object} return data
-     */
-    Object writeResourceDataPassThroughRunningForCmHandle(String cmHandleId,
-                                                          String resourceIdentifier,
-                                                          OperationType operationType,
-                                                          String requestBody,
-                                                          String contentType,
-                                                          String authorization);
-
-    /**
-     * Retrieve module references for the given cm handle.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return a collection of modules names and revisions
-     */
-    Collection<ModuleReference> getYangResourcesModuleReferences(String cmHandleId);
-
-    /**
-     * Retrieve module definitions for the given cm handle.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return a collection of module definition (moduleName, revision and yang resource content)
-     */
-    Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId);
-
-    /**
-     * Get module definitions for the given parameters.
-     *
-     * @param cmHandleId        cm-handle identifier
-     * @param moduleName        module name
-     * @param moduleRevision    the revision of the module
-     * @return list of module definitions (module name, revision, yang resource content)
-     */
-    Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(String cmHandleId,
-                                                                         String moduleName,
-                                                                         String moduleRevision);
-
-    /**
-     * Query cm handle details by cm handle's name.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return a collection of cm handle details.
-     */
-    NcmpServiceCmHandle getNcmpServiceCmHandle(String cmHandleId);
-
-    /**
-     * Get cm handle public properties by cm handle id.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return a collection of cm handle public properties.
-     */
-    Map<String, String> getCmHandlePublicProperties(String cmHandleId);
-
-    /**
-     * Get cm handle composite state by cm handle id.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return a cm handle composite state
-     */
-    CompositeState getCmHandleCompositeState(String cmHandleId);
-
-    /**
-     * Query and return cm handles that match the given query parameters.
-     *
-     * @param cmHandleQueryApiParameters the cm handle query parameters
-     * @return collection of cm handles
-     */
-    Collection<NcmpServiceCmHandle> executeCmHandleSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters);
-
-    /**
-     * Query and return cm handle ids that match the given query parameters.
-     *
-     * @param cmHandleQueryApiParameters the cm handle query parameters
-     * @return collection of cm handle ids
-     */
-    Collection<String> executeCmHandleIdSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters);
-
-    /**
-     * Set the data sync enabled flag, along with the data sync state to true or false based on the cm handle id.
-     *
-     * @param cmHandleId cm handle id
-     * @param dataSyncEnabled data sync enabled flag
-     */
-    void setDataSyncEnabled(String cmHandleId, Boolean dataSyncEnabled);
-
-    /**
-     * Get all cm handle IDs by DMI plugin identifier.
-     *
-     * @param dmiPluginIdentifier DMI plugin identifier
-     * @return collection of cm handle IDs
-     */
-    Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier);
-
-    /**
-     * Get all cm handle IDs by various search criteria.
-     *
-     * @param cmHandleQueryServiceParameters cm handle query parameters
-     * @return collection of cm handle IDs
-     */
-    Collection<String> executeCmHandleIdSearchForInventory(CmHandleQueryServiceParameters
-                                                               cmHandleQueryServiceParameters);
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyQueryService.java
deleted file mode 100644 (file)
index 340806b..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- *  ============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;
-
-import org.onap.cps.spi.FetchDescendantsOption;
-
-/*
- * Datastore interface for handling cached CPS data query requests.
- */
-public interface NetworkCmProxyQueryService {
-
-    /**
-     * Get resource data for operational.
-     *
-     * @param cmHandleId cm handle identifier
-     * @param cpsPath cps path
-     * @Link FetchDescendantsOption fetch descendants option
-     * @return {@code Object} resource data
-     */
-    Object queryResourceDataOperational(String cmHandleId,
-                                      String cpsPath,
-                                      FetchDescendantsOption fetchDescendantsOption);
-}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.data.models;
 
-public record CmResourceAddress(String datastoreName, String cmHandleId, String resourceIdentifier) {
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.config.CpsApplicationContext;
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher;
 
+@Getter
+@RequiredArgsConstructor
+public class CmResourceAddress {
+
+    private final String datastoreName;
+    @Getter(AccessLevel.NONE)
+    private final String cmHandleReference;
+    private final String resourceIdentifier;
+
+    public String getResolvedCmHandleId() {
+        return CpsApplicationContext.getCpsBean(AlternateIdMatcher.class).getCmHandleId(cmHandleReference);
+    }
 }
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.data.models;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.data.models;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.api.data.models;
 
 
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import lombok.Getter;
-import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException;
+import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException;
 
 @Getter
 public enum DatastoreType {
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.api.data.models;
 
 import com.fasterxml.jackson.annotation.JsonValue;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Locale;
 import lombok.Getter;
-import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException;
+import org.onap.cps.ncmp.api.data.exceptions.InvalidOperationException;
 
 @Getter
 public enum OperationType {
@@ -48,13 +46,6 @@ public enum OperationType {
         return String.valueOf(operationName);
     }
 
-    private static final Map<String, OperationType> operationNameToOperationEnum = new HashMap<>();
-
-    static {
-        Arrays.stream(OperationType.values()).forEach(
-                operationType -> operationNameToOperationEnum.put(operationType.getOperationName(), operationType));
-    }
-
     /**
      * From operation name get operation enum type.
      *
@@ -62,10 +53,10 @@ public enum OperationType {
      * @return the operation enum type
      */
     public static OperationType fromOperationName(final String operationName) {
-        final OperationType operationType = operationNameToOperationEnum.get(operationName);
-        if (null == operationType) {
+        try {
+            return OperationType.valueOf(operationName.toUpperCase(Locale.ENGLISH));
+        } catch (final IllegalArgumentException e) {
             throw new InvalidOperationException(operationName + " is an invalid operation name");
         }
-        return operationType;
     }
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobResultService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobResultService.java
new file mode 100644 (file)
index 0000000..c6b7a8b
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.datajobs;
+
+/**
+ * Service interface to retrieve the result of a data job.
+ * The operations interact with a DMI Plugin to retrieve data job results.
+ */
+public interface DataJobResultService {
+
+    /**
+     * Retrieves the result of a specific data job.
+     *
+     * @param authorization     The authorization header from the REST request.
+     * @param dmiServiceName    The name of the DMI Service relevant to the data job.
+     * @param dataProducerId    The ID of the producer registered by DMI, used for operations related to this request.
+     *                          This could include alternate IDs or specific identifiers.
+     * @param dataProducerJobId The identifier of the data producer job within the DMI system.
+     * @param destination       The destination of the results: Kafka topic name or S3 bucket name.
+     * @return The result of the data job.
+     */
+    String getDataJobResult(final String authorization,
+                            final String dmiServiceName,
+                            final String dataProducerId,
+                            final String dataProducerJobId,
+                            final String destination);
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api;
+package org.onap.cps.ncmp.api.datajobs;
 
-import org.onap.cps.ncmp.api.models.datajob.DataJobMetadata;
-import org.onap.cps.ncmp.api.models.datajob.DataJobReadRequest;
-import org.onap.cps.ncmp.api.models.datajob.DataJobWriteRequest;
+import java.util.List;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobReadRequest;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest;
+import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse;
 
 public interface DataJobService {
 
     /**
      * process read data job operations.
      *
-     * @param dataJobId          Unique identifier of the job within the request
+     * @param authorization      the authorization header from the REST request
+     * @param dataJobId          unique identifier of the job within the request
      * @param dataJobMetadata    data job request headers
      * @param dataJobReadRequest read data job request
      */
-    void readDataJob(String dataJobId, DataJobMetadata dataJobMetadata, DataJobReadRequest dataJobReadRequest);
+    void readDataJob(String authorization,
+                     String dataJobId,
+                     DataJobMetadata dataJobMetadata,
+                     DataJobReadRequest dataJobReadRequest);
 
     /**
      * process write data job operations.
      *
-     * @param dataJobId           Unique identifier of the job within the request
+     * @param authorization       the authorization header from the REST request
+     * @param dataJobId           unique identifier of the job within the request
      * @param dataJobMetadata     data job request headers
      * @param dataJobWriteRequest write data job request
+     * @return a list of sub-job write responses
      */
-    void writeDataJob(String dataJobId, DataJobMetadata dataJobMetadata, DataJobWriteRequest dataJobWriteRequest);
+    List<SubJobWriteResponse> writeDataJob(String authorization,
+                                           String dataJobId,
+                                           DataJobMetadata dataJobMetadata,
+                                           DataJobWriteRequest dataJobWriteRequest);
 }
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobStatusService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/DataJobStatusService.java
new file mode 100644 (file)
index 0000000..9cfc49f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.datajobs;
+
+/**
+ * Service interface to check the status of a data job.
+ * The operations interact with a DMI Plugin to retrieve data job statuses.
+ */
+public interface DataJobStatusService {
+
+    /**
+     * Retrieves the current status of a specific data job.
+     *
+     * @param authorization     The authorization header from the REST request.
+     * @param dmiServiceName    The name of the DMI Service relevant to the data job.
+     * @param dataProducerId    The ID of the producer registered by DMI, used for operations related to this request.
+     *                          This could include alternate IDs or specific identifiers.
+     * @param dataProducerJobId The identifier of the data producer job within the DMI system.
+     * @return The current status of the data job as a String.
+     */
+    String getDataJobStatus(final String authorization,
+                            final String dmiServiceName,
+                            final String dataProducerId,
+                            final String dataProducerJobId);
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DmiWriteOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/DmiWriteOperation.java
new file mode 100644 (file)
index 0000000..7e9ca79
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.datajobs.models;
+
+import java.util.Map;
+
+/**
+ * Describes the write data job operation to be forwarded to dmi.
+ *
+ * @param path               Identifier of a managed object (MO) on a network element. Defines the resource on which
+ *                           operation is executed. Typically, is Fully Distinguished Name (FDN).
+ * @param op                 Describes the operation to execute.  The value can be as below:
+ *                           e.g. "add", "replace", "remove", "action" etc.
+ * @param moduleSetTag       The module set tag of the CM Handle.
+ * @param value              The value to be written depends on the type of operation.
+ * @param operationId        Unique identifier of the operation within the request.
+ * @param privateProperties  Contains the private properties of a Cm Handle.
+ */
+public record DmiWriteOperation(
+        String path,
+        String op,
+        String moduleSetTag,
+        Object value,
+        String operationId,
+        Map<String, String> privateProperties) {}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ProducerKey.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/ProducerKey.java
new file mode 100644 (file)
index 0000000..ac6b7f8
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.datajobs.models;
+
+/**
+ * Composite key created from the DMI Service name and a data producer identifier.
+ * Helps to group of the sub job request for a given DMI Plugin.
+ *
+ * @param dmiServiceName          Describes the name of the relevant DMI service.
+ * @param dataProducerIdentifier  The name of a data producer identifier from a Cm Handle.
+ */
+public record ProducerKey(String dmiServiceName, String dataProducerIdentifier) {
+
+    @Override
+    public String toString() {
+        return dmiServiceName + "#"  + dataProducerIdentifier;
+    }
+}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteRequest.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteRequest.java
new file mode 100644 (file)
index 0000000..a7a6573
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.datajobs.models;
+
+import java.util.Collection;
+
+/**
+ * Request data for a write operation by the DMI Plugin.
+ *
+ * @param destination     The destination of the results. ( e.g. S3 Bucket)
+ * @param dataAcceptType  Define the data response accept type.
+ *                        e.g. "application/vnd.3gpp.object-tree-hierarchical+json",
+ *                        "application/vnd.3gpp.object-tree-flat+json" etc.
+ * @param dataContentType Define the data request content type.
+ *                        e.g. "application/3gpp-json-patch+json" etc.
+ * @param dataProducerId  Identifier of the data producer.
+ * @param dataJobId       Identifier for the overall Datajob
+ * @param data            A collection of outgoing write operations.
+ */
+public record SubJobWriteRequest (
+        String destination,
+        String dataAcceptType,
+        String dataContentType,
+        String dataProducerId,
+        String dataJobId,
+        Collection<DmiWriteOperation> data) {}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/datajobs/models/SubJobWriteResponse.java
new file mode 100644 (file)
index 0000000..9cdd8e0
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.datajobs.models;
+
+/**
+ * Request data for a write operation towards DMI Plugin.
+ *
+ * @param subJobId        Identifier of the sub-job from DMI.
+ * @param dmiServiceName  The provided name of the DMI service from the request.
+ * @param dataProducerId  Identifier of the data producer.
+ */
+public record SubJobWriteResponse(String subJobId, String dmiServiceName, String dataProducerId) {}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/DmiClientRequestException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/DmiClientRequestException.java
new file mode 100644 (file)
index 0000000..65ccba8
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 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.exceptions;
+
+import lombok.Getter;
+import org.onap.cps.ncmp.api.NcmpResponseStatus;
+
+/**
+ * Http Client Request exception from dmi service.
+ */
+@Getter
+public class DmiClientRequestException extends NcmpException {
+
+    private static final long serialVersionUID = 6659897770659834797L;
+    final NcmpResponseStatus ncmpResponseStatus;
+    final String message;
+    final String responseBodyAsString;
+    final int httpStatusCode;
+
+    /**
+     * Constructor to form exception for dmi service response.
+     *
+     * @param httpStatusCode       http response code from the client
+     * @param message              response message from the client
+     * @param responseBodyAsString response body from the client
+     * @param ncmpResponseStatus   ncmp status message and code
+     */
+    public DmiClientRequestException(final int httpStatusCode, final String message, final String responseBodyAsString,
+                                     final NcmpResponseStatus ncmpResponseStatus) {
+        super(message, responseBodyAsString);
+        this.httpStatusCode = httpStatusCode;
+        this.message = message;
+        this.responseBodyAsString = responseBodyAsString;
+        this.ncmpResponseStatus = ncmpResponseStatus;
+    }
+}
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2022 Nordix Foundation
+ * Copyright (C) 2024 Nordix Foundation
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.exception;
+package org.onap.cps.ncmp.api.exceptions;
 
 import lombok.Getter;
 
 /**
- * Http Client Request exception for passthrough scenarios.
+ * Exception to be used when policy execution fails or does not allow to proceed.
  */
 @Getter
-public class HttpClientRequestException extends NcmpException {
+public class PolicyExecutorException extends NcmpException {
 
-    private static final long serialVersionUID = 6659897770659834797L;
-    final Integer httpStatus;
+    private static final long serialVersionUID = 6659897770659834798L;
 
     /**
-     * Constructor to form exception for passthrough scenarios.
+     * Constructor to form exception for policy executor responses.
      *
-     * @param message    message details from NCMP
-     * @param details    response body from the client available as details
-     * @param httpStatus http status code from the client
+     * @param message response message
+     * @param details response details
      */
-    public HttpClientRequestException(final String message, final String details, final Integer httpStatus) {
+    public PolicyExecutorException(final String message, final String details) {
         super(message, details);
-        this.httpStatus = httpStatus;
     }
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/DataJobServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/DataJobServiceImpl.java
deleted file mode 100644 (file)
index b4377b8..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl;
-
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.DataJobService;
-import org.onap.cps.ncmp.api.models.datajob.DataJobMetadata;
-import org.onap.cps.ncmp.api.models.datajob.DataJobReadRequest;
-import org.onap.cps.ncmp.api.models.datajob.DataJobWriteRequest;
-
-@Slf4j
-public class DataJobServiceImpl implements DataJobService {
-
-    @Override
-    public void readDataJob(final String dataJobId, final DataJobMetadata dataJobMetadata,
-                            final DataJobReadRequest dataJobReadRequest) {
-        log.info("data job id for read operation is: {}", dataJobId);
-    }
-
-    @Override
-    public void writeDataJob(final String dataJobId, final DataJobMetadata dataJobMetadata,
-                             final DataJobWriteRequest dataJobWriteRequest) {
-        log.info("data job id for write operation is: {}", dataJobId);
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
deleted file mode 100644 (file)
index 798a280..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
- *  Modifications 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.impl.client;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import java.util.Locale;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
-import org.onap.cps.ncmp.api.impl.operations.OperationType;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
-
-@Component
-@RequiredArgsConstructor
-@Slf4j
-public class DmiRestClient {
-
-    private static final String HEALTH_CHECK_URL_EXTENSION = "/actuator/health";
-    private static final String NOT_SPECIFIED = "";
-    private final RestTemplate restTemplate;
-    private final DmiProperties dmiProperties;
-
-    /**
-     * Sends POST operation to DMI with json body containing module references.
-     *
-     * @param dmiResourceUrl          dmi resource url
-     * @param requestBodyAsJsonString json data body
-     * @param operationType           the type of operation being executed (for error reporting only)
-     * @param authorization           contents of Authorization header, or null if not present
-     * @return response entity of type String
-     */
-    public ResponseEntity<Object> postOperationWithJsonData(final String dmiResourceUrl,
-                                                            final String requestBodyAsJsonString,
-                                                            final OperationType operationType,
-                                                            final String authorization) {
-        final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders(),
-                authorization));
-        try {
-            return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class);
-        } catch (final HttpStatusCodeException httpStatusCodeException) {
-            final String exceptionMessage = "Unable to " + operationType.toString() + " resource data.";
-            throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(),
-                httpStatusCodeException.getStatusCode().value());
-        }
-    }
-
-    /**
-     * Get DMI plugin health status.
-     *
-     * @param       dmiPluginBaseUrl the base URL of the dmi-plugin
-     * @return      plugin health status ("UP" is all OK, "" (not-specified) in case of any exception)
-     */
-    public String getDmiHealthStatus(final String dmiPluginBaseUrl) {
-        final HttpEntity<Object> httpHeaders = new HttpEntity<>(configureHttpHeaders(new HttpHeaders(), null));
-        try {
-            final JsonNode responseHealthStatus =
-                restTemplate.getForObject(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION,
-                    JsonNode.class, httpHeaders);
-            return responseHealthStatus == null ? NOT_SPECIFIED :
-                responseHealthStatus.get("status").asText();
-        } catch (final Exception e) {
-            log.warn("Failed to retrieve health status from {}. Error Message: {}", dmiPluginBaseUrl, e.getMessage());
-            return NOT_SPECIFIED;
-        }
-    }
-
-    private HttpHeaders configureHttpHeaders(final HttpHeaders httpHeaders, final String authorization) {
-        if (dmiProperties.isDmiBasicAuthEnabled()) {
-            httpHeaders.setBasicAuth(dmiProperties.getAuthUsername(), dmiProperties.getAuthPassword());
-        } else if (authorization != null && authorization.toLowerCase(Locale.getDefault()).startsWith("bearer ")) {
-            httpHeaders.add(HttpHeaders.AUTHORIZATION, authorization);
-        }
-        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
-        return httpHeaders;
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java
deleted file mode 100644 (file)
index d547e31..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.impl.config;
-
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
-import lombok.Getter;
-import lombok.Setter;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.convert.DurationUnit;
-
-@Getter
-@Setter
-@ConfigurationProperties(prefix = "ncmp.dmi.httpclient", ignoreUnknownFields = true)
-public class HttpClientConfiguration {
-
-    /**
-     * The maximum time to establish a connection.
-     */
-    @DurationUnit(ChronoUnit.SECONDS)
-    private Duration connectionTimeoutInSeconds = Duration.ofSeconds(180);
-
-    /**
-     * The maximum number of open connections per route.
-     */
-    private int maximumConnectionsPerRoute = 50;
-
-    /**
-     * The maximum total number of open connections.
-     */
-    private int maximumConnectionsTotal = maximumConnectionsPerRoute * 2;
-
-    /**
-     * The duration after which idle connections are evicted.
-     */
-    @DurationUnit(ChronoUnit.SECONDS)
-    private Duration idleConnectionEvictionThresholdInSeconds = Duration.ofSeconds(5);
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java
deleted file mode 100644 (file)
index c6ff116..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 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.impl.config;
-
-import java.util.Arrays;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.hc.client5.http.config.ConnectionConfig;
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
-import org.apache.hc.client5.http.impl.classic.HttpClients;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Scope;
-import org.springframework.http.MediaType;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.stereotype.Component;
-import org.springframework.web.client.RestTemplate;
-
-@Configuration
-@EnableConfigurationProperties(HttpClientConfiguration.class)
-@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-public class NcmpConfiguration {
-
-    @Getter
-    @Component
-    public static class DmiProperties {
-        @Value("${ncmp.dmi.auth.username}")
-        private String authUsername;
-        @Value("${ncmp.dmi.auth.password}")
-        private String authPassword;
-        @Value("${ncmp.dmi.api.base-path}")
-        private String dmiBasePath;
-        @Value("${ncmp.dmi.auth.enabled}")
-        private boolean dmiBasicAuthEnabled;
-    }
-
-    /**
-     * Rest template bean.
-     *
-     * @param restTemplateBuilder the rest template builder
-     * @param httpClientConfiguration the http client configuration
-     * @return rest template instance
-     */
-    @Bean
-    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
-    public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder,
-                                            final HttpClientConfiguration httpClientConfiguration) {
-
-        final ConnectionConfig connectionConfig = ConnectionConfig.copy(ConnectionConfig.DEFAULT)
-                .setConnectTimeout(Timeout.of(httpClientConfiguration.getConnectionTimeoutInSeconds()))
-                .build();
-
-        final PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
-                .setDefaultConnectionConfig(connectionConfig)
-                .setMaxConnTotal(httpClientConfiguration.getMaximumConnectionsTotal())
-                .setMaxConnPerRoute(httpClientConfiguration.getMaximumConnectionsPerRoute())
-                .build();
-
-        final CloseableHttpClient httpClient = HttpClients.custom()
-                .setConnectionManager(connectionManager)
-                .evictExpiredConnections()
-                .evictIdleConnections(
-                        TimeValue.of(httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds()))
-                .build();
-
-        final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
-
-        final RestTemplate restTemplate = restTemplateBuilder
-                .requestFactory(() -> requestFactory)
-                .setConnectTimeout(httpClientConfiguration.getConnectionTimeoutInSeconds())
-                .build();
-
-        setRestTemplateMessageConverters(restTemplate);
-        return restTemplate;
-    }
-
-    private static void setRestTemplateMessageConverters(final RestTemplate restTemplate) {
-        final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =
-            new MappingJackson2HttpMessageConverter();
-        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(
-            Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN));
-        restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
-    }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDelta.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDelta.java
deleted file mode 100644 (file)
index 8a4beb9..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
-import org.springframework.stereotype.Component;
-
-@Component
-@RequiredArgsConstructor
-public class CmNotificationSubscriptionDelta {
-
-    private final CmNotificationSubscriptionPersistenceService cmNotificationSubscriptionPersistenceService;
-
-    /**
-     * Get the delta for a given predicates list.
-     *
-     * @param dmiCmNotificationSubscriptionPredicates list of DmiCmNotificationSubscriptionPredicates
-     * @return delta list of DmiCmNotificationSubscriptionPredicates
-     */
-    public List<DmiCmNotificationSubscriptionPredicate> getDelta(
-        final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates) {
-        final List<DmiCmNotificationSubscriptionPredicate> delta = new ArrayList<>();
-
-        for (final DmiCmNotificationSubscriptionPredicate cmNotificationSubscriptionPredicate:
-            dmiCmNotificationSubscriptionPredicates) {
-
-            final Set<String> targetCmHandleIds = new HashSet<>();
-            final Set<String> xpaths = new HashSet<>();
-            final DatastoreType datastoreType = cmNotificationSubscriptionPredicate.getDatastoreType();
-
-            for (final String cmHandleId : cmNotificationSubscriptionPredicate.getTargetCmHandleIds()) {
-                for (final String xpath : cmNotificationSubscriptionPredicate.getXpaths()) {
-                    if (!cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,
-                            cmHandleId, xpath)) {
-                        xpaths.add(xpath);
-                        targetCmHandleIds.add(cmHandleId);
-
-                    }
-                }
-            }
-
-            final DmiCmNotificationSubscriptionPredicate predicateDelta =
-                new DmiCmNotificationSubscriptionPredicate(targetCmHandleIds, datastoreType, xpaths);
-
-            delta.add(predicateDelta);
-        }
-        return delta;
-    }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandler.java
deleted file mode 100644 (file)
index 50a5df5..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (c) 2024 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.impl.events.cmsubscription;
-
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
-import org.springframework.stereotype.Component;
-
-@Component
-@RequiredArgsConstructor
-public class CmNotificationSubscriptionEventsHandler {
-    private final CmNotificationSubscriptionNcmpOutEventProducer cmNotificationSubscriptionNcmpOutEventProducer;
-    private final CmNotificationSubscriptionDmiInEventProducer cmNotificationSubscriptionDmiInEventProducer;
-
-    /**
-     * Publish the event to the client who requested the subscription with key as subscription id and event is Cloud
-     * Event compliant.
-     *
-     * @param subscriptionId                         Cm Subscription id
-     * @param eventType                              Type of event
-     * @param cmNotificationSubscriptionNcmpOutEvent Cm Notification Subscription Event for the
-     *                                               client
-     * @param isScheduledEvent                       Determines if the event is to be scheduled
-     *                                               or published now
-     */
-    public void publishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, final String eventType,
-                                                              final CmNotificationSubscriptionNcmpOutEvent
-                                                                      cmNotificationSubscriptionNcmpOutEvent,
-                                                              final boolean isScheduledEvent) {
-        cmNotificationSubscriptionNcmpOutEventProducer.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId,
-                eventType, cmNotificationSubscriptionNcmpOutEvent, isScheduledEvent);
-    }
-
-    /**
-     * Publish the event to the provided dmi plugin with key as subscription id and the event is in Cloud Event format.
-     *
-     * @param subscriptionId                       Cm Subscription id
-     * @param dmiPluginName                        Dmi Plugin Name
-     * @param eventType                            Type of event
-     * @param cmNotificationSubscriptionDmiInEvent Cm Notification Subscription event for Dmi
-     */
-    public void publishCmNotificationSubscriptionDmiInEvent(final String subscriptionId, final String dmiPluginName,
-                                                            final String eventType,
-                                                            final CmNotificationSubscriptionDmiInEvent
-                                                                    cmNotificationSubscriptionDmiInEvent) {
-        cmNotificationSubscriptionDmiInEventProducer.publishCmNotificationSubscriptionDmiInEvent(subscriptionId,
-                dmiPluginName, eventType, cmNotificationSubscriptionDmiInEvent);
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandler.java
deleted file mode 100644 (file)
index 73f9563..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription;
-
-import java.util.List;
-import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
-import org.springframework.stereotype.Component;
-
-@Component
-@RequiredArgsConstructor
-public class CmNotificationSubscriptionMappersHandler {
-
-    private final CmNotificationSubscriptionDmiInEventMapper cmNotificationSubscriptionDmiInEventMapper;
-    private final CmNotificationSubscriptionNcmpOutEventMapper cmNotificationSubscriptionNcmpOutEventMapper;
-
-    /**
-     * Mapper to form a request for the DMI Plugin for the Cm Notification Subscription.
-     *
-     * @param dmiCmNotificationSubscriptionPredicates Collection of Cm Notification Subscription predicates
-     * @return cm notification subscription dmi in event
-     */
-    public CmNotificationSubscriptionDmiInEvent toCmNotificationSubscriptionDmiInEvent(
-            final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates) {
-        return cmNotificationSubscriptionDmiInEventMapper.toCmNotificationSubscriptionDmiInEvent(
-                dmiCmNotificationSubscriptionPredicates);
-    }
-
-    /**
-     * Mapper to form a response for the client for the Cm Notification Subscription.
-     *
-     * @param subscriptionId                          Cm Notification Subscription id
-     * @param dmiCmNotificationSubscriptionDetailsMap contains CmNotificationSubscriptionDetails per dmi plugin
-     * @return CmNotificationSubscriptionNcmpOutEvent to sent back to the client
-     */
-    public CmNotificationSubscriptionNcmpOutEvent toCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId,
-         final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap) {
-        return cmNotificationSubscriptionNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId,
-                dmiCmNotificationSubscriptionDetailsMap);
-    }
-
-    /**
-     * Mapper to form a rejected response for the client for the Cm Notification Subscription Request.
-     *
-     * @param subscriptionId subscription id
-     * @param rejectedTargetFilters list of rejected target filters for the subscription request
-     * @return to sent back to the client
-     */
-    public CmNotificationSubscriptionNcmpOutEvent toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(
-            final String subscriptionId, final List<String> rejectedTargetFilters) {
-        return cmNotificationSubscriptionNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(
-                subscriptionId, rejectedTargetFilters);
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventPublishingTask.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventPublishingTask.java
deleted file mode 100644 (file)
index f7dd51e..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription;
-
-import static org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer.buildAndGetCmNotificationNcmpOutEventAsCloudEvent;
-
-import io.cloudevents.CloudEvent;
-import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.events.EventsPublisher;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
-import org.onap.cps.utils.JsonObjectMapper;
-
-@Slf4j
-@RequiredArgsConstructor
-public class CmNotificationSubscriptionNcmpOutEventPublishingTask implements Runnable {
-
-    private final String topicName;
-    private final String subscriptionId;
-    private final String eventType;
-    private final EventsPublisher<CloudEvent> eventsPublisher;
-    private final JsonObjectMapper jsonObjectMapper;
-    private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler;
-    private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler;
-
-    /**
-     * Delegating the responsibility of publishing CmNotificationSubscriptionNcmpOutEvent as a separate task which will
-     * be called after a specified delay.
-     */
-    @Override
-    public void run() {
-        final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap =
-                dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId);
-        final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent =
-                cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId,
-                        dmiCmNotificationSubscriptionDetailsMap);
-        eventsPublisher.publishCloudEvent(topicName, subscriptionId,
-                buildAndGetCmNotificationNcmpOutEventAsCloudEvent(jsonObjectMapper, subscriptionId, eventType,
-                        cmNotificationSubscriptionNcmpOutEvent));
-        dmiCmNotificationSubscriptionCacheHandler
-                .removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(subscriptionId);
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java
deleted file mode 100644 (file)
index b5370bf..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription;
-
-import static org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus.PENDING;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.Predicate;
-import org.springframework.stereotype.Component;
-
-@Component
-@RequiredArgsConstructor
-public class DmiCmNotificationSubscriptionCacheHandler {
-
-    private final CmNotificationSubscriptionPersistenceService cmNotificationSubscriptionPersistenceService;
-    private final Map<String, Map<String, DmiCmNotificationSubscriptionDetails>> cmNotificationSubscriptionCache;
-    private final InventoryPersistence inventoryPersistence;
-
-    /**
-     * Adds new subscription to the subscription cache.
-     *
-     * @param subscriptionId    subscription id
-     * @param predicates        subscription request predicates
-     */
-    public void add(final String subscriptionId, final List<Predicate> predicates) {
-        cmNotificationSubscriptionCache.put(subscriptionId, createDmiCmNotificationSubscriptionsPerDmi(predicates));
-    }
-
-    /**
-     * Get cm notification subscription cache entry via subscription id.
-     *
-     * @param subscriptionId    subscription id
-     * @return map of dmi cm notification subscriptions per dmi
-     */
-    public Map<String, DmiCmNotificationSubscriptionDetails> get(final String subscriptionId) {
-        return cmNotificationSubscriptionCache.get(subscriptionId);
-    }
-
-
-    /**
-     * Remove cache entries with CmNotificationSubscriptionStatus ACCEPTED/REJECTED via subscription id.
-     *
-     * @param subscriptionId subscription id as key in CM notification Subscription cache.
-     */
-    public void removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(final String subscriptionId) {
-        final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionsPerDmi =
-                cmNotificationSubscriptionCache.get(subscriptionId);
-        final Map<String, DmiCmNotificationSubscriptionDetails> updatedDmiCmNotificationSubscriptionsPerDmi =
-                dmiCmNotificationSubscriptionsPerDmi.entrySet().stream().filter(
-                                dmiCmNotificationSubscription ->
-                                        !isAcceptedOrRejected(dmiCmNotificationSubscription.getValue()))
-                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-        cmNotificationSubscriptionCache.put(subscriptionId, updatedDmiCmNotificationSubscriptionsPerDmi);
-    }
-
-    /**
-     *  Creates map of subscription details per DMI.
-     *
-     * @param predicates    CM Subscription Create Request Predicates
-     * @return              Map of DmiCmNotificationSubscription per DMI plugin
-     */
-    public Map<String, DmiCmNotificationSubscriptionDetails> createDmiCmNotificationSubscriptionsPerDmi(
-            final List<Predicate> predicates) {
-        final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi =
-                new HashMap<>();
-        for (final Predicate requestPredicate : predicates) {
-            final List<String> targetFilter = requestPredicate.getTargetFilter();
-            final DatastoreType datastoreType = DatastoreType.fromDatastoreName(
-                    requestPredicate.getScopeFilter().getDatastore().toString());
-            final Set<String> xpaths = new HashSet<>(requestPredicate.getScopeFilter().getXpathFilter());
-            final Map<String, Set<String>> targetCmHandlesByDmiMap = groupTargetCmHandleIdsByDmi(targetFilter);
-            for (final Map.Entry<String, Set<String>> targetCmHandlesByDmi: targetCmHandlesByDmiMap.entrySet()) {
-                final DmiCmNotificationSubscriptionPredicate dmiCmNotificationSubscriptionPredicate =
-                        new DmiCmNotificationSubscriptionPredicate(targetCmHandlesByDmi.getValue(),
-                                datastoreType, xpaths);
-                updateDmiCmNotificationSubscriptionDetailsPerDmi(targetCmHandlesByDmi.getKey(),
-                        dmiCmNotificationSubscriptionPredicate,
-                        dmiCmNotificationSubscriptionDetailsPerDmi);
-            }
-        }
-        return dmiCmNotificationSubscriptionDetailsPerDmi;
-    }
-
-    /**
-     *  Update status in map of subscription details per DMI.
-     *
-     * @param subscriptionId    String of subscription Id
-     * @param dmiServiceName    String of dmiServiceName
-     * @param status            String of status
-     *
-     */
-    public void updateDmiCmNotificationSubscriptionStatusPerDmi(final String subscriptionId,
-            final String dmiServiceName, final CmNotificationSubscriptionStatus status) {
-        final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi =
-                cmNotificationSubscriptionCache.get(subscriptionId);
-        dmiCmNotificationSubscriptionDetailsPerDmi.get(dmiServiceName).setCmNotificationSubscriptionStatus(status);
-        cmNotificationSubscriptionCache.put(subscriptionId, dmiCmNotificationSubscriptionDetailsPerDmi);
-
-    }
-
-    /**
-     *  Persist map of subscription details per DMI.
-     *
-     * @param subscriptionId    String of subscription Id
-     * @param dmiServiceName    String of dmiServiceName
-     *
-     */
-    public void persistIntoDatabasePerDmi(final String subscriptionId, final String dmiServiceName) {
-        final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicateList =
-                cmNotificationSubscriptionCache.get(subscriptionId).get(dmiServiceName)
-                        .getDmiCmNotificationSubscriptionPredicates();
-        for (final DmiCmNotificationSubscriptionPredicate dmiCmNotificationSubscriptionPredicate:
-                dmiCmNotificationSubscriptionPredicateList) {
-            final DatastoreType datastoreType = dmiCmNotificationSubscriptionPredicate.getDatastoreType();
-            final Set<String> cmHandles = dmiCmNotificationSubscriptionPredicate.getTargetCmHandleIds();
-            final Set<String> xpaths = dmiCmNotificationSubscriptionPredicate.getXpaths();
-
-            for (final String cmHandle: cmHandles) {
-                for (final String xpath: xpaths) {
-                    cmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(datastoreType, cmHandle,
-                            xpath, subscriptionId);
-                }
-            }
-        }
-    }
-
-    private void updateDmiCmNotificationSubscriptionDetailsPerDmi(
-            final String dmiServiceName,
-            final DmiCmNotificationSubscriptionPredicate dmiCmNotificationSubscriptionPredicate,
-            final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi) {
-        if (dmiCmNotificationSubscriptionDetailsPerDmi.containsKey(dmiServiceName)) {
-            dmiCmNotificationSubscriptionDetailsPerDmi.get(dmiServiceName)
-                    .getDmiCmNotificationSubscriptionPredicates().add(dmiCmNotificationSubscriptionPredicate);
-        } else {
-            dmiCmNotificationSubscriptionDetailsPerDmi.put(dmiServiceName,
-                    new DmiCmNotificationSubscriptionDetails(
-                            new ArrayList<>(List.of(dmiCmNotificationSubscriptionPredicate)),
-                            PENDING));
-        }
-    }
-
-    private Map<String, Set<String>> groupTargetCmHandleIdsByDmi(final List<String> targetCmHandleIds) {
-        final Map<String, Set<String>> targetCmHandlesByDmiServiceNames = new HashMap<>();
-        final Collection<YangModelCmHandle> yangModelCmHandles =
-                inventoryPersistence.getYangModelCmHandles(targetCmHandleIds);
-
-        for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) {
-            final String dmiServiceName = yangModelCmHandle.getDmiServiceName();
-            targetCmHandlesByDmiServiceNames.putIfAbsent(dmiServiceName, new HashSet<>());
-            targetCmHandlesByDmiServiceNames.get(dmiServiceName).add(yangModelCmHandle.getId());
-        }
-        return targetCmHandlesByDmiServiceNames;
-    }
-
-    private boolean isAcceptedOrRejected(
-            final DmiCmNotificationSubscriptionDetails dmiCmNotificationSubscription) {
-        return dmiCmNotificationSubscription.getCmNotificationSubscriptionStatus().toString().equals("ACCEPTED")
-                || dmiCmNotificationSubscription.getCmNotificationSubscriptionStatus().toString().equals("REJECTED");
-    }
-}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java
deleted file mode 100644 (file)
index fb89aae..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription.consumer;
-
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent;
-
-import io.cloudevents.CloudEvent;
-import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.kafka.clients.consumer.ConsumerRecord;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
-import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.stereotype.Component;
-
-@Component
-@Slf4j
-@RequiredArgsConstructor
-public class CmNotificationSubscriptionDmiOutEventConsumer {
-
-    private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler;
-    private final CmNotificationSubscriptionEventsHandler cmNotificationSubscriptionEventsHandler;
-    private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler;
-
-    /**
-     * Consume the Cm Notification Subscription event from the dmi-plugin.
-     *
-     * @param cmNotificationSubscriptionDmiOutEventConsumerRecord the event to be consumed
-     */
-    @KafkaListener(topics = "${app.ncmp.avc.subscription-response-topic}",
-            containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
-    public void consumeCmNotificationSubscriptionDmiOutEvent(
-            final ConsumerRecord<String, CloudEvent> cmNotificationSubscriptionDmiOutEventConsumerRecord) {
-        final CloudEvent cloudEvent = cmNotificationSubscriptionDmiOutEventConsumerRecord.value();
-        final CmNotificationSubscriptionDmiOutEvent cmNotificationSubscriptionDmiOutEvent =
-                toTargetEvent(cloudEvent, CmNotificationSubscriptionDmiOutEvent.class);
-        final String correlationId = String.valueOf(cloudEvent.getExtension("correlationid"));
-        if ("subscriptionCreateResponse".equals(cloudEvent.getType()) && cmNotificationSubscriptionDmiOutEvent != null
-                    && correlationId != null) {
-            handleCmSubscriptionCreate(correlationId, cmNotificationSubscriptionDmiOutEvent);
-        }
-    }
-
-    private void handleCmSubscriptionCreate(final String correlationId,
-            final CmNotificationSubscriptionDmiOutEvent cmNotificationSubscriptionDmiOutEvent) {
-        final String subscriptionId = correlationId.split("#")[0];
-        final String dmiPluginName = correlationId.split("#")[1];
-
-        if ("ACCEPTED".equals(cmNotificationSubscriptionDmiOutEvent.getData().getStatusMessage())) {
-            handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmNotificationSubscriptionStatus.ACCEPTED);
-            dmiCmNotificationSubscriptionCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName);
-            handleEventsStatusPerDmi(subscriptionId);
-        }
-
-        if ("REJECTED".equals(cmNotificationSubscriptionDmiOutEvent.getData().getStatusMessage())) {
-            handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmNotificationSubscriptionStatus.REJECTED);
-            handleEventsStatusPerDmi(subscriptionId);
-        }
-
-        log.info("Cm Subscription with id : {} handled by the dmi-plugin : {} has the status : {}", subscriptionId,
-                dmiPluginName, cmNotificationSubscriptionDmiOutEvent.getData().getStatusMessage());
-    }
-
-    private void handleCacheStatusPerDmi(final String subscriptionId, final String dmiPluginName,
-            final CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus) {
-        dmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi(subscriptionId,
-                dmiPluginName, cmNotificationSubscriptionStatus);
-    }
-
-    private void handleEventsStatusPerDmi(final String subscriptionId) {
-        final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi =
-                dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId);
-        final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent =
-                cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId,
-                        dmiCmNotificationSubscriptionDetailsPerDmi);
-        cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId,
-                "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false);
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapper.java
deleted file mode 100644 (file)
index ea21751..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription.mapper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.Data;
-import org.springframework.stereotype.Component;
-
-@Component
-@RequiredArgsConstructor
-public class CmNotificationSubscriptionNcmpOutEventMapper {
-
-    /**
-     * Mapper to form a response for the client for the Cm Notification Subscription.
-     *
-     * @param subscriptionId                          Cm Notification Subscription Id
-     * @param dmiCmNotificationSubscriptionDetailsMap contains CmNotificationSubscriptionDetails per dmi plugin
-     * @return CmNotificationSubscriptionNcmpOutEvent to sent back to the client
-     */
-    public CmNotificationSubscriptionNcmpOutEvent toCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId,
-            final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap) {
-
-        final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent =
-                new CmNotificationSubscriptionNcmpOutEvent();
-        final Data cmSubscriptionData = new Data();
-        cmSubscriptionData.setSubscriptionId(subscriptionId);
-        populateCmNotificationSubscriptionNcmpOutEventWithCmHandleIds(dmiCmNotificationSubscriptionDetailsMap,
-                cmSubscriptionData);
-        cmNotificationSubscriptionNcmpOutEvent.setData(cmSubscriptionData);
-
-        return cmNotificationSubscriptionNcmpOutEvent;
-    }
-
-    /**
-     * Mapper to form a rejected response for the client for the Cm Notification Subscription Request.
-     *
-     * @param subscriptionId subscription id
-     * @param rejectedTargetFilters list of rejected target filters for the subscription request
-     * @return to sent back to the client
-     */
-    public CmNotificationSubscriptionNcmpOutEvent toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(
-            final String subscriptionId, final List<String> rejectedTargetFilters) {
-        final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent =
-                new CmNotificationSubscriptionNcmpOutEvent();
-        final Data cmSubscriptionData = new Data();
-        cmSubscriptionData.setSubscriptionId(subscriptionId);
-        cmSubscriptionData.setRejectedTargets(rejectedTargetFilters);
-        cmNotificationSubscriptionNcmpOutEvent.setData(cmSubscriptionData);
-        return cmNotificationSubscriptionNcmpOutEvent;
-    }
-
-    private void populateCmNotificationSubscriptionNcmpOutEventWithCmHandleIds(
-            final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap,
-            final Data cmSubscriptionData) {
-
-        final List<String> acceptedCmHandleIds = new ArrayList<>();
-        final List<String> pendingCmHandleIds = new ArrayList<>();
-        final List<String> rejectedCmHandleIds = new ArrayList<>();
-
-        dmiCmNotificationSubscriptionDetailsMap.forEach((dmiPluginName, dmiCmNotificationSubscriptionDetails) -> {
-            final CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus =
-                    dmiCmNotificationSubscriptionDetails.getCmNotificationSubscriptionStatus();
-            final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates =
-                    dmiCmNotificationSubscriptionDetails.getDmiCmNotificationSubscriptionPredicates();
-
-            switch (cmNotificationSubscriptionStatus) {
-                case ACCEPTED -> acceptedCmHandleIds.addAll(
-                        extractCmHandleIds(dmiCmNotificationSubscriptionPredicates));
-                case PENDING -> pendingCmHandleIds.addAll(extractCmHandleIds(dmiCmNotificationSubscriptionPredicates));
-                default -> rejectedCmHandleIds.addAll(extractCmHandleIds(dmiCmNotificationSubscriptionPredicates));
-            }
-        });
-
-        cmSubscriptionData.setAcceptedTargets(acceptedCmHandleIds);
-        cmSubscriptionData.setPendingTargets(pendingCmHandleIds);
-        cmSubscriptionData.setRejectedTargets(rejectedCmHandleIds);
-
-    }
-
-    private List<String> extractCmHandleIds(
-            final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates) {
-        final List<String> cmHandleIds = new ArrayList<>();
-        dmiCmNotificationSubscriptionPredicates.forEach(dmiCmNotificationSubscriptionPredicate -> cmHandleIds.addAll(
-                dmiCmNotificationSubscriptionPredicate.getTargetCmHandleIds()));
-
-        return cmHandleIds;
-    }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java
deleted file mode 100644 (file)
index ac5de07..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription.producer;
-
-import io.cloudevents.CloudEvent;
-import io.cloudevents.core.builder.CloudEventBuilder;
-import java.net.URI;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.events.EventsPublisher;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionNcmpOutEventPublishingTask;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
-import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.stereotype.Component;
-
-@Component
-@Slf4j
-@RequiredArgsConstructor
-@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
-public class CmNotificationSubscriptionNcmpOutEventProducer {
-
-    @Value("${app.ncmp.avc.subscription-outcome-topic}")
-    private String cmNotificationSubscriptionNcmpOutEventTopic;
-
-    @Value("${ncmp.timers.subscription-forwarding.dmi-response-timeout-ms}")
-    private Integer cmNotificationSubscriptionDmiOutEventTimeoutInMs;
-
-    private final EventsPublisher<CloudEvent> eventsPublisher;
-    private final JsonObjectMapper jsonObjectMapper;
-    private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler;
-    private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler;
-    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
-    private static final Map<String, ScheduledFuture<?>> scheduledTasksPerSubscriptionId = new ConcurrentHashMap<>();
-
-    /**
-     * Publish the event to the client who requested the subscription with key as subscription id and event is Cloud
-     * Event compliant.
-     *
-     * @param subscriptionId                         Cm Subscription Id
-     * @param eventType                              Type of event
-     * @param cmNotificationSubscriptionNcmpOutEvent Cm Notification Subscription Event for the
-     *                                               client
-     * @param isScheduledEvent                       Determines if the event is to be scheduled
-     *                                               or published now
-     */
-    public void publishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, final String eventType,
-                                                              final CmNotificationSubscriptionNcmpOutEvent
-                                                                      cmNotificationSubscriptionNcmpOutEvent,
-                                                              final boolean isScheduledEvent) {
-
-        if (isScheduledEvent && !scheduledTasksPerSubscriptionId.containsKey(subscriptionId)) {
-            final ScheduledFuture<?> scheduledFuture =
-                    scheduleAndPublishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, eventType);
-            scheduledTasksPerSubscriptionId.putIfAbsent(subscriptionId, scheduledFuture);
-            log.debug("Scheduled the CmNotificationSubscriptionEvent for subscriptionId : {}", subscriptionId);
-        } else {
-            cancelScheduledTaskForSubscriptionId(subscriptionId);
-            publishCmNotificationSubscriptionNcmpOutEventNow(subscriptionId, eventType,
-                    cmNotificationSubscriptionNcmpOutEvent);
-            log.info("Published CmNotificationSubscriptionEvent on demand for subscriptionId : {}", subscriptionId);
-        }
-    }
-
-    private ScheduledFuture<?> scheduleAndPublishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId,
-                                                                                        final String eventType) {
-        final CmNotificationSubscriptionNcmpOutEventPublishingTask
-                cmNotificationSubscriptionNcmpOutEventPublishingTask =
-                new CmNotificationSubscriptionNcmpOutEventPublishingTask(cmNotificationSubscriptionNcmpOutEventTopic,
-                        subscriptionId, eventType, eventsPublisher, jsonObjectMapper,
-                        cmNotificationSubscriptionMappersHandler, dmiCmNotificationSubscriptionCacheHandler);
-        return scheduledExecutorService.schedule(cmNotificationSubscriptionNcmpOutEventPublishingTask,
-                cmNotificationSubscriptionDmiOutEventTimeoutInMs, TimeUnit.MILLISECONDS);
-    }
-
-    private void cancelScheduledTaskForSubscriptionId(final String subscriptionId) {
-
-        final ScheduledFuture<?> scheduledFuture = scheduledTasksPerSubscriptionId.get(subscriptionId);
-        if (scheduledFuture != null) {
-            scheduledFuture.cancel(true);
-            scheduledTasksPerSubscriptionId.remove(subscriptionId);
-        }
-
-    }
-
-
-    private void publishCmNotificationSubscriptionNcmpOutEventNow(final String subscriptionId, final String eventType,
-                                                                  final CmNotificationSubscriptionNcmpOutEvent
-                                                                          cmNotificationSubscriptionNcmpOutEvent) {
-        final CloudEvent cmNotificationSubscriptionNcmpOutEventAsCloudEvent =
-                buildAndGetCmNotificationNcmpOutEventAsCloudEvent(jsonObjectMapper, subscriptionId, eventType,
-                        cmNotificationSubscriptionNcmpOutEvent);
-        eventsPublisher.publishCloudEvent(cmNotificationSubscriptionNcmpOutEventTopic, subscriptionId,
-                cmNotificationSubscriptionNcmpOutEventAsCloudEvent);
-        dmiCmNotificationSubscriptionCacheHandler
-                .removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(subscriptionId);
-    }
-
-    /**
-     * Get an NCMP out event as cloud event.
-     *
-     * @param jsonObjectMapper  JSON object mapper
-     * @param subscriptionId subscription id
-     * @param eventType event type
-     * @param cmNotificationSubscriptionNcmpOutEvent cm notification subscription NCMP out event
-     * @return cm notification subscription NCMP out event as cloud event
-     */
-    public static CloudEvent buildAndGetCmNotificationNcmpOutEventAsCloudEvent(
-            final JsonObjectMapper jsonObjectMapper, final String subscriptionId, final String eventType,
-            final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent) {
-
-        return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withType(eventType)
-                .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.cm.subscription:1.0.0"))
-                .withExtension("correlationid", subscriptionId)
-                .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionNcmpOutEvent)).build();
-    }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java
deleted file mode 100644 (file)
index 7872ba0..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription.service;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionDelta;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.Predicate;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent;
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
-import org.springframework.stereotype.Service;
-
-@Service
-@RequiredArgsConstructor
-public class CmNotificationSubscriptionHandlerServiceImpl implements CmNotificationSubscriptionHandlerService {
-
-    private final CmNotificationSubscriptionPersistenceService cmNotificationSubscriptionPersistenceService;
-    private final CmNotificationSubscriptionDelta cmNotificationSubscriptionDelta;
-    private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler;
-    private final CmNotificationSubscriptionEventsHandler cmNotificationSubscriptionEventsHandler;
-    private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler;
-
-    @Override
-    public void processSubscriptionCreateRequest(
-            final CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent) {
-        final String subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId();
-        final List<Predicate> predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates();
-
-        if (cmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId(subscriptionId)) {
-            dmiCmNotificationSubscriptionCacheHandler.add(subscriptionId, predicates);
-            sendSubscriptionCreateRequestToDmi(subscriptionId);
-            scheduleCmNotificationSubscriptionNcmpOutEventResponse(subscriptionId);
-        } else {
-            rejectAndPublishCmNotificationSubscriptionCreateRequest(subscriptionId,
-                    predicates);
-        }
-    }
-
-    private void scheduleCmNotificationSubscriptionNcmpOutEventResponse(final String subscriptionId) {
-        cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId,
-                "subscriptionCreateResponse", null, true);
-    }
-
-    private void rejectAndPublishCmNotificationSubscriptionCreateRequest(final String subscriptionId,
-            final List<Predicate> predicates) {
-        final Set<String> subscriptionTargetFilters =
-                predicates.stream().flatMap(predicate -> predicate.getTargetFilter().stream())
-                        .collect(Collectors.toSet());
-        final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent =
-                cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(
-                        subscriptionId, new ArrayList<>(subscriptionTargetFilters));
-        cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId,
-                "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false);
-    }
-
-    private void sendSubscriptionCreateRequestToDmi(final String subscriptionId) {
-        final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap =
-                dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId);
-        dmiCmNotificationSubscriptionDetailsMap.forEach((dmiPluginName, dmiCmNotificationSubscriptionDetails) -> {
-            final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates =
-                    cmNotificationSubscriptionDelta.getDelta(
-                            dmiCmNotificationSubscriptionDetails.getDmiCmNotificationSubscriptionPredicates());
-            final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent =
-                    cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionDmiInEvent(
-                            dmiCmNotificationSubscriptionPredicates);
-            cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(subscriptionId,
-                    dmiPluginName, "subscriptionCreateRequest", cmNotificationSubscriptionDmiInEvent);
-        });
-    }
-}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java
deleted file mode 100644 (file)
index 3bb40c3..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription.service;
-
-import java.util.Collection;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
-
-public interface CmNotificationSubscriptionPersistenceService {
-
-    String NCMP_DATASPACE_NAME = "NCMP-Admin";
-    String CM_SUBSCRIPTIONS_ANCHOR_NAME = "cm-data-subscriptions";
-
-    /**
-     * Check if we have an ongoing cm subscription based on the parameters.
-     *
-     * @param datastoreType the susbcription target datastore type
-     * @param cmHandleId the id of the cm handle for the susbcription
-     * @param xpath the target xpath
-     * @return true for ongoing cmsubscription , otherwise false
-     */
-    boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
-            final String xpath);
-
-    /**
-     * Check if the subscription ID is unique against ongoing subscriptions.
-     *
-     * @param subscriptionId subscription ID
-     * @return true if subscriptionId is not used in active subscriptions, otherwise false
-     */
-    boolean isUniqueSubscriptionId(final String subscriptionId);
-
-    /**
-     * Get all ongoing cm notification subscription based on the parameters.
-     *
-     * @param datastoreType the susbcription target datastore type
-     * @param cmHandleId the id of the cm handle for the susbcription
-     * @param xpath the target xpath
-     * @return collection of subscription ids of ongoing cm notification subscription
-     */
-    Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType,
-            final String cmHandleId, final String xpath);
-
-    /**
-     * Add cm notification subscription.
-     *
-     * @param datastoreType the susbcription target datastore type
-     * @param cmHandleId the id of the cm handle for the susbcription
-     * @param xpath the target xpath
-     * @param newSubscriptionId subscription id to be added
-     */
-    void addCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
-                                       final String xpath, final String newSubscriptionId);
-
-    /**
-     * Remove cm notification Subscription.
-     *
-     * @param datastoreType the susbcription target datastore type
-     * @param cmHandleId the id of the cm handle for the susbcription
-     * @param xpath the target xpath
-     * @param subscriptionId subscription id to remove
-     */
-    void removeCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
-                                          final String xpath, final String subscriptionId);
-
-}
-
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
deleted file mode 100644 (file)
index a9ec124..0000000
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation
- *  Modifications 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.impl.operations;
-
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING;
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA;
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ;
-
-import io.micrometer.core.annotation.Timed;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.NcmpResponseStatus;
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
-import org.onap.cps.ncmp.api.impl.utils.data.operation.ResourceDataOperationRequestUtils;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.CmResourceAddress;
-import org.onap.cps.ncmp.api.models.DataOperationRequest;
-import org.onap.cps.spi.exceptions.CpsException;
-import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.util.UriComponentsBuilder;
-
-/**
- * Operations class for DMI data.
- */
-@Component
-@Slf4j
-public class DmiDataOperations extends DmiOperations {
-
-    public DmiDataOperations(final InventoryPersistence inventoryPersistence,
-                             final JsonObjectMapper jsonObjectMapper,
-                             final NcmpConfiguration.DmiProperties dmiProperties,
-                             final DmiRestClient dmiRestClient,
-                             final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
-        super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder);
-    }
-
-    /**
-     * This method fetches the resource data from operational data store for given cm handle
-     * identifier on given resource using dmi client.
-     *
-     * @param cmResourceAddress   target datastore, cm handle and resource identifier
-     * @param optionsParamInQuery options query
-     * @param topicParamInQuery   topic name for (triggering) async responses
-     * @param requestId           requestId for async responses
-     * @param authorization       contents of Authorization header, or null if not present
-     * @return {@code ResponseEntity} response entity
-     */
-    @Timed(value = "cps.ncmp.dmi.get",
-            description = "Time taken to fetch the resource data from operational data store for given cm handle "
-                    + "identifier on given resource using dmi client")
-    public ResponseEntity<Object> getResourceDataFromDmi(final CmResourceAddress cmResourceAddress,
-                                                         final String optionsParamInQuery,
-                                                         final String topicParamInQuery,
-                                                         final String requestId,
-                                                         final String authorization) {
-        final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmResourceAddress.cmHandleId());
-        final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
-        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle);
-        final String dmiResourceDataUrl = getDmiRequestUrl(cmResourceAddress.datastoreName(),
-            cmResourceAddress.cmHandleId(), cmResourceAddress.resourceIdentifier(), optionsParamInQuery,
-                topicParamInQuery, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, authorization);
-    }
-
-    /**
-     * This method fetches all the resource data from operational data store for given cm handle
-     * identifier using dmi client.
-     *
-     * @param dataStoreName data store name
-     * @param cmHandleId    network resource identifier
-     * @param requestId     requestId for async responses
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName,
-                                                         final String cmHandleId,
-                                                         final String requestId) {
-        final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
-        final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null,
-                yangModelCmHandle);
-        final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/",
-                null, null,
-                yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
-        final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
-        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, null);
-    }
-
-    /**
-     * This method requests the resource data by data store for given list of cm handles using dmi client.
-     * The data wil be returned as message on the topic specified.
-     *
-     * @param topicParamInQuery        topic name for (triggering) async responses
-     * @param dataOperationRequest     data operation request to execute operations
-     * @param requestId                requestId for as a response
-     * @param authorization            contents of Authorization header, or null if not present
-     */
-    public void requestResourceDataFromDmi(final String topicParamInQuery,
-                                           final DataOperationRequest dataOperationRequest,
-                                           final String requestId,
-                                           final String authorization)  {
-
-        final Set<String> cmHandlesIds
-                = getDistinctCmHandleIdsFromDataOperationRequest(dataOperationRequest);
-
-        final Collection<YangModelCmHandle> yangModelCmHandles
-                = inventoryPersistence.getYangModelCmHandles(cmHandlesIds);
-
-        final Map<String, List<DmiDataOperation>> operationsOutPerDmiServiceName
-                = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(topicParamInQuery,
-                requestId, dataOperationRequest, yangModelCmHandles);
-
-        buildDataOperationRequestUrlAndSendToDmiService(topicParamInQuery, requestId, operationsOutPerDmiServiceName,
-                authorization);
-    }
-
-    /**
-     * This method creates the resource data from pass-through running data store for given cm handle
-     * identifier on given resource using dmi client.
-     *
-     * @param cmHandleId    network resource identifier
-     * @param resourceId    resource identifier
-     * @param operationType operation enum
-     * @param requestData   the request data
-     * @param dataType      data type
-     * @param authorization contents of Authorization header, or null if not present
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<Object> writeResourceDataPassThroughRunningFromDmi(final String cmHandleId,
-                                                                             final String resourceId,
-                                                                             final OperationType operationType,
-                                                                             final String requestData,
-                                                                             final String dataType,
-                                                                             final String authorization) {
-        final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
-        final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType,
-                yangModelCmHandle);
-        final String dmiUrl = getDmiRequestUrl(PASSTHROUGH_RUNNING.getDatastoreName(), cmHandleId, resourceId,
-                null, null,
-                yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
-        final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
-        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operationType, authorization);
-    }
-
-    private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
-        return inventoryPersistence.getYangModelCmHandle(cmHandleId);
-    }
-
-    private String getDmiRequestBody(final OperationType operationType,
-                                     final String requestId,
-                                     final String requestData,
-                                     final String dataType,
-                                     final YangModelCmHandle yangModelCmHandle) {
-        final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
-                .operationType(operationType)
-                .requestId(requestId)
-                .data(requestData)
-                .dataType(dataType)
-                .build();
-        dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties());
-        return jsonObjectMapper.asJsonString(dmiRequestBody);
-    }
-
-    private String getDmiRequestUrl(final String dataStoreName,
-                                    final String cmHandleId,
-                                    final String resourceId,
-                                    final String optionsParamInQuery,
-                                    final String topicParamInQuery,
-                                    final String dmiServiceName) {
-        return dmiServiceUrlBuilder.getDmiDatastoreUrl(
-                dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery,
-                        topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName,
-                        cmHandleId));
-    }
-
-    private String getDmiServiceDataOperationRequestUrl(final String dmiServiceName,
-                                                        final String topicParamInQuery,
-                                                        final String requestId) {
-        final MultiValueMap<String, String> dataOperationRequestQueryParams = dmiServiceUrlBuilder
-                .getDataOperationRequestQueryParams(topicParamInQuery, requestId);
-        return dmiServiceUrlBuilder.getDataOperationRequestUrl(dataOperationRequestQueryParams,
-                dmiServiceUrlBuilder.populateDataOperationRequestUriVariables(dmiServiceName));
-    }
-
-    private void validateIfCmHandleStateReady(final YangModelCmHandle yangModelCmHandle,
-                                              final CmHandleState cmHandleState) {
-        if (cmHandleState != CmHandleState.READY) {
-            throw new CpsException("State mismatch exception.", "Cm-Handle not in READY state. "
-                    + "cm handle state is "
-                    + yangModelCmHandle.getCompositeState().getCmHandleState());
-        }
-    }
-
-    private static Set<String> getDistinctCmHandleIdsFromDataOperationRequest(final DataOperationRequest
-                                                                              dataOperationRequest) {
-        return dataOperationRequest.getDataOperationDefinitions().stream()
-                .flatMap(dataOperationDefinition ->
-                        dataOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet());
-    }
-
-    private void buildDataOperationRequestUrlAndSendToDmiService(final String topicParamInQuery,
-                                                                 final String requestId,
-                                                                 final Map<String, List<DmiDataOperation>>
-                                                                groupsOutPerDmiServiceName,
-                                                                 final String authorization) {
-
-        groupsOutPerDmiServiceName.forEach((dmiServiceName, dmiDataOperationRequestBodies) -> {
-            final String dmiDataOperationResourceUrl =
-                    getDmiServiceDataOperationRequestUrl(dmiServiceName, topicParamInQuery, requestId);
-            sendDataOperationRequestToDmiService(dmiDataOperationResourceUrl, dmiDataOperationRequestBodies,
-                    authorization);
-        });
-    }
-
-    private void sendDataOperationRequestToDmiService(final String dataOperationResourceUrl,
-                                                      final List<DmiDataOperation> dmiDataOperationRequestBodies,
-                                                      final String authorization) {
-        final DmiDataOperationRequest dmiDataOperationRequest = DmiDataOperationRequest.builder()
-                .operations(dmiDataOperationRequestBodies).build();
-        final String dmiDataOperationRequestAsJsonString =
-                jsonObjectMapper.asJsonString(dmiDataOperationRequest);
-        try {
-            dmiRestClient.postOperationWithJsonData(dataOperationResourceUrl, dmiDataOperationRequestAsJsonString, READ,
-                    authorization);
-        } catch (final Exception exception) {
-            handleTaskCompletionException(exception, dataOperationResourceUrl, dmiDataOperationRequestBodies);
-        }
-    }
-
-    private void handleTaskCompletionException(final Throwable throwable,
-                                               final String dataOperationResourceUrl,
-                                               final List<DmiDataOperation> dmiDataOperationRequestBodies) {
-        if (throwable != null) {
-            final MultiValueMap<String, String> dataOperationResourceUrlParameters =
-                    UriComponentsBuilder.fromUriString(dataOperationResourceUrl).build().getQueryParams();
-            final String topicName = dataOperationResourceUrlParameters.get("topic").get(0);
-            final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0);
-
-            final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>>
-                    cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
-
-            dmiDataOperationRequestBodies.forEach(dmiDataOperationRequestBody -> {
-                final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream()
-                        .map(CmHandle::getId).toList();
-                if (throwable.getCause() instanceof HttpClientRequestException) {
-                    cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
-                            Map.of(UNABLE_TO_READ_RESOURCE_DATA, cmHandleIds));
-                } else {
-                    cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
-                            Map.of(DMI_SERVICE_NOT_RESPONDING, cmHandleIds));
-                }
-            });
-            ResourceDataOperationRequestUtils.publishErrorMessageToClientTopic(topicName, requestId,
-                    cmHandleIdsPerResponseCodesPerOperation);
-        }
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java
deleted file mode 100644 (file)
index c8d73ea..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
- *  Modifications 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.impl.operations;
-
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
-import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.stereotype.Service;
-
-@RequiredArgsConstructor
-@Service
-public class DmiOperations {
-
-    protected final InventoryPersistence inventoryPersistence;
-    protected final JsonObjectMapper jsonObjectMapper;
-    protected final NcmpConfiguration.DmiProperties dmiProperties;
-    protected final DmiRestClient dmiRestClient;
-    protected final DmiServiceUrlBuilder dmiServiceUrlBuilder;
-
-    String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) {
-        return dmiServiceUrlBuilder.getResourceDataBasePathUriBuilder()
-                .pathSegment("{resourceName}")
-                .buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString();
-    }
-
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java
deleted file mode 100644 (file)
index 60f39fc..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * ============LICENSE_START========================================================
- * Copyright (c) 2024 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.impl.utils;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
-import org.springframework.stereotype.Service;
-
-@Service
-@Slf4j
-@RequiredArgsConstructor
-public class AlternateIdChecker {
-
-    public enum Operation {
-        CREATE, UPDATE
-    }
-
-    private final InventoryPersistence inventoryPersistence;
-
-    private static final String NO_CURRENT_ALTERNATE_ID = "";
-
-    /**
-     * Check if the alternate can be applied to the given cm handle (id).
-     * Conditions:
-     * - proposed alternate is blank (it wil be ignored)
-     * - proposed alternate is same as current (no change)
-     * - proposed alternate is not in use for a different cm handle (in the DB)
-     *
-     * @param cmHandleId cm handle id
-     * @param proposedAlternateId proposed alternate id
-     * @return true if the new alternate id not in use or equal to current alternate id, false otherwise
-     */
-    public boolean canApplyAlternateId(final String cmHandleId, final String proposedAlternateId) {
-        String currentAlternateId = "";
-        try {
-            final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
-            currentAlternateId = yangModelCmHandle.getAlternateId();
-        } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
-            // work with blank current alternate id
-        }
-        return this.canApplyAlternateId(cmHandleId, currentAlternateId, proposedAlternateId);
-    }
-
-    /**
-     * Check if the alternate can be applied to the given cm handle.
-     * Conditions:
-     * - proposed alternate is blank (it wil be ignored)
-     * - proposed alternate is same as current (no change)
-     * - proposed alternate is not in use for a different cm handle (in the DB)
-     *
-     * @param cmHandleId   cm handle id
-     * @param currentAlternateId current alternate id
-     * @param proposedAlternateId new alternate id
-     * @return true if the new alternate id not in use or equal to current alternate id, false otherwise
-     */
-    public boolean canApplyAlternateId(final String cmHandleId,
-                                       final String currentAlternateId,
-                                       final String proposedAlternateId) {
-        if (StringUtils.isBlank(currentAlternateId)) {
-            if (alternateIdAlreadyInDb(proposedAlternateId)) {
-                log.warn("Alternate id update ignored, cannot update cm handle {}, alternate id is already "
-                    + "assigned to a different cm handle", cmHandleId);
-                return false;
-            }
-            return true;
-        }
-        if (currentAlternateId.equals(proposedAlternateId)) {
-            return true;
-        }
-        log.warn("Alternate id update ignored, cannot update cm handle {}, already has an alternate id of {}",
-            cmHandleId, currentAlternateId);
-        return false;
-    }
-
-    /**
-     * Check all alternate ids of a batch of cm handles.
-     * Includes cross-checks in the batch itself for duplicates. Only the first entry encountered wil be accepted.
-     *
-     * @param newNcmpServiceCmHandles the proposed new cm handles
-     * @param operation type of operation being executed
-     * @return collection of cm handles ids which are acceptable
-     */
-    public Collection<String> getIdsOfCmHandlesWithRejectedAlternateId(
-                                    final Collection<NcmpServiceCmHandle> newNcmpServiceCmHandles,
-                                    final Operation operation) {
-        final Set<String> acceptedAlternateIds = new HashSet<>(newNcmpServiceCmHandles.size());
-        final Collection<String> rejectedCmHandleIds = new ArrayList<>();
-        for (final NcmpServiceCmHandle ncmpServiceCmHandle : newNcmpServiceCmHandles) {
-            final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
-            final String proposedAlternateId = ncmpServiceCmHandle.getAlternateId();
-            if (isProposedAlternateIdAcceptable(proposedAlternateId, operation, acceptedAlternateIds, cmHandleId)) {
-                acceptedAlternateIds.add(proposedAlternateId);
-            } else {
-                rejectedCmHandleIds.add(cmHandleId);
-            }
-        }
-        return rejectedCmHandleIds;
-    }
-
-    private boolean isProposedAlternateIdAcceptable(final String proposedAlternateId, final Operation operation,
-                                                    final Set<String> acceptedAlternateIds, final String cmHandleId) {
-        if (StringUtils.isEmpty(proposedAlternateId)) {
-            return true;
-        }
-        if (acceptedAlternateIds.contains(proposedAlternateId)) {
-            log.warn("Alternate id update ignored, cannot update cm handle {}, alternate id is already "
-                + "assigned to a different cm handle (in this batch)", cmHandleId);
-            return false;
-        }
-        if (Operation.CREATE.equals(operation)) {
-            return canApplyAlternateId(cmHandleId, NO_CURRENT_ALTERNATE_ID, proposedAlternateId);
-        }
-        return canApplyAlternateId(cmHandleId, proposedAlternateId);
-    }
-
-    private boolean alternateIdAlreadyInDb(final String alternateId) {
-        try {
-            inventoryPersistence.getCmHandleDataNodeByAlternateId(alternateId);
-        } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
-            return false;
-        }
-        return true;
-    }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DataNodeHelper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DataNodeHelper.java
deleted file mode 100644 (file)
index c032d1e..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.impl.utils;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-import org.onap.cps.spi.model.DataNode;
-
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class DataNodeHelper {
-
-    /**
-     * The nested DataNode object is being flattened.
-     *
-     * @param dataNode object.
-     * @return DataNode as stream.
-     */
-    public static Stream<DataNode> flatten(final DataNode dataNode) {
-        return Stream.concat(Stream.of(dataNode),
-                dataNode.getChildDataNodes().stream().flatMap(DataNodeHelper::flatten));
-    }
-
-    /**
-     * The leaves for each DataNode is listed as map.
-     *
-     * @param dataNodes as collection
-     * @return list of map for the all leaves
-     */
-    public static List<Map<String, Serializable>> getDataNodeLeaves(final Collection<DataNode> dataNodes) {
-        return dataNodes.stream()
-                .flatMap(DataNodeHelper::flatten)
-                .map(DataNode::getLeaves)
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * Extracts the mapping of cm handle id to status with details from nodes leaves.
-     *
-     * @param dataNodeLeaves as a list of map
-     * @return cm handle id to status and details mapping
-     */
-    public static Map<String, Map<String, String>> cmHandleIdToStatusAndDetailsAsMap(
-            final List<Map<String, Serializable>> dataNodeLeaves) {
-        return dataNodeLeaves.stream()
-                .filter(entryset -> entryset.values().contains("PENDING")
-                        || entryset.values().contains("ACCEPTED")
-                        || entryset.values().contains("REJECTED"))
-                .collect(
-                        HashMap<String, Map<String, String>>::new,
-                        (result, entry) -> {
-                            final String cmHandleId = (String) entry.get("cmHandleId");
-                            final String status = (String) entry.get("status");
-                            final String details = (String) entry.get("details");
-
-                            if (cmHandleId != null && status != null) {
-                                result.put(cmHandleId, new HashMap<>());
-                                result.get(cmHandleId).put("status", status);
-                                result.get(cmHandleId).put("details", details == null ? "" : details);
-                            }
-                        },
-                        HashMap::putAll
-                );
-    }
-
-    /**
-     * Extracts the mapping of cm handle id to status with details from data node collection.
-     *
-     * @param dataNodes as a collection
-     * @return cm handle id to status and details mapping
-     */
-    public static Map<String, Map<String, String>> cmHandleIdToStatusAndDetailsAsMapFromDataNode(
-            final Collection<DataNode> dataNodes) {
-        return cmHandleIdToStatusAndDetailsAsMap(getDataNodeLeaves(dataNodes));
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
deleted file mode 100644 (file)
index 04acaa5..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 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.impl.utils;
-
-import java.util.HashMap;
-import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import org.apache.logging.log4j.util.Strings;
-import org.apache.logging.log4j.util.TriConsumer;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
-import org.onap.cps.spi.utils.CpsValidator;
-import org.springframework.stereotype.Component;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.util.UriComponentsBuilder;
-
-@Component
-@RequiredArgsConstructor
-public class DmiServiceUrlBuilder {
-
-    private final NcmpConfiguration.DmiProperties dmiProperties;
-    private final CpsValidator cpsValidator;
-
-    /**
-     * This method creates the dmi service url.
-     *
-     * @param queryParams  query param map as key,value pair
-     * @param uriVariables uri param map as key (placeholder),value pair
-     * @return {@code String} dmi service url as string
-     */
-    public String getDmiDatastoreUrl(final MultiValueMap<String, String> queryParams,
-                                     final Map<String, Object> uriVariables) {
-        return getUriComponentsBuilder(getResourceDataBasePathUriBuilder(), queryParams, uriVariables)
-                .buildAndExpand().toUriString();
-    }
-
-    /**
-     * This method builds data operation request url.
-     *
-     * @param dataoperationRequestQueryParams  query param map as key, value pair
-     * @param dataoperationRequestUriVariables uri param map as key (placeholder), value pair
-     * @return {@code String} data operation request url as string
-     */
-    public String getDataOperationRequestUrl(final MultiValueMap<String, String> dataoperationRequestQueryParams,
-                                             final Map<String, Object> dataoperationRequestUriVariables) {
-        return getDataOperationResourceDataBasePathUriBuilder()
-                .queryParams(dataoperationRequestQueryParams)
-                .uriVariables(dataoperationRequestUriVariables)
-                .buildAndExpand().toUriString();
-    }
-
-    /**
-     * This method creates the dmi service url builder object with path variables.
-     *
-     * @return {@code UriComponentsBuilder} dmi service url builder object
-     */
-    public UriComponentsBuilder getResourceDataBasePathUriBuilder() {
-        return UriComponentsBuilder.newInstance()
-                .path("{dmiServiceName}")
-                .pathSegment("{dmiBasePath}")
-                .pathSegment("v1")
-                .pathSegment("ch")
-                .pathSegment("{cmHandleId}");
-    }
-
-    /**
-     * This method creates the dmi service url builder object with path variables for data operation request.
-     *
-     * @return {@code UriComponentsBuilder} dmi service url builder object
-     */
-    public UriComponentsBuilder getDataOperationResourceDataBasePathUriBuilder() {
-        return UriComponentsBuilder.newInstance()
-                .path("{dmiServiceName}")
-                .pathSegment("{dmiBasePath}")
-                .pathSegment("v1")
-                .pathSegment("data");
-    }
-
-    /**
-     * This method populates uri variables.
-     *
-     * @param dataStoreName data store name
-     * @param dmiServiceName dmi service name
-     * @param cmHandleId        cm handle id for dmi registration
-     * @return {@code String} dmi service url as string
-     */
-    public Map<String, Object> populateUriVariables(final String dataStoreName,
-                                                    final String dmiServiceName,
-                                                    final String cmHandleId) {
-        cpsValidator.validateNameCharacters(cmHandleId);
-        final Map<String, Object> uriVariables = new HashMap<>();
-        final String dmiBasePath = dmiProperties.getDmiBasePath();
-        uriVariables.put("dmiServiceName", dmiServiceName);
-        uriVariables.put("dmiBasePath", dmiBasePath);
-        uriVariables.put("cmHandleId", cmHandleId);
-        uriVariables.put("dataStore", dataStoreName);
-        return uriVariables;
-    }
-
-    /**
-     * This method populates uri variables for data operation request.
-     *
-     * @param dmiServiceName dmi service name
-     * @return {@code Map<String, Object>} uri variables as map
-     */
-    public Map<String, Object> populateDataOperationRequestUriVariables(final String dmiServiceName) {
-        final Map<String, Object> uriVariables = new HashMap<>();
-        final String dmiBasePath = dmiProperties.getDmiBasePath();
-        uriVariables.put("dmiServiceName", dmiServiceName);
-        uriVariables.put("dmiBasePath", dmiBasePath);
-        return uriVariables;
-    }
-
-    /**
-     * This method is used to populate map from query params.
-     *
-     * @param resourceId          unique id of response for valid topic
-     * @param optionsParamInQuery options into url param
-     * @param topicParamInQuery   topic into url param
-     * @return all valid query params as map
-     */
-    public MultiValueMap<String, String> populateQueryParams(final String resourceId,
-                                                             final String optionsParamInQuery,
-                                                             final String topicParamInQuery) {
-        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
-        getQueryParamConsumer().accept("resourceIdentifier",
-                resourceId, queryParams);
-        getQueryParamConsumer().accept("options", optionsParamInQuery, queryParams);
-        if (Strings.isNotEmpty(topicParamInQuery)) {
-            getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams);
-        }
-        return queryParams;
-    }
-
-    /**
-     * This method is used to populate map from query params for data operation request.
-     *
-     * @param topicParamInQuery topic into url param
-     * @param requestId         unique id of response for valid topic
-     * @return all valid query params as map
-     */
-    public MultiValueMap<String, String> getDataOperationRequestQueryParams(final String topicParamInQuery,
-                                                                            final String requestId) {
-        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
-        getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams);
-        getQueryParamConsumer().accept("requestId", requestId, queryParams);
-        return queryParams;
-    }
-
-    private TriConsumer<String, String, MultiValueMap<String, String>> getQueryParamConsumer() {
-        return (paramName, paramValue, paramMap) -> {
-            if (Strings.isNotEmpty(paramValue)) {
-                paramMap.add(paramName, paramValue);
-            }
-        };
-    }
-
-    private UriComponentsBuilder getUriComponentsBuilder(final UriComponentsBuilder uriComponentsBuilder,
-                                                         final MultiValueMap<String, String> queryParams,
-                                                         final Map<String, Object> uriVariables) {
-        return uriComponentsBuilder
-                .pathSegment("data")
-                .pathSegment("ds")
-                .pathSegment("{dataStore}")
-                .queryParams(queryParams)
-                .uriVariables(uriVariables);
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmDataSubscriptionEvent.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmDataSubscriptionEvent.java
deleted file mode 100644 (file)
index e527d99..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.impl.yangmodels;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-/**
- * Subscription event model to persist data into DB.
- * Yang model subscription event
- */
-@Getter
-@Setter
-@NoArgsConstructor
-@JsonInclude(Include.NON_NULL)
-@EqualsAndHashCode(onlyExplicitlyIncluded = true)
-public class YangModelCmDataSubscriptionEvent {
-
-    @EqualsAndHashCode.Include
-    @JsonProperty("name")
-    private String name;
-
-    private List<CmHandle> cmHandles;
-
-    @AllArgsConstructor
-    @Data
-    @JsonInclude(JsonInclude.Include.NON_NULL)
-    public static class CmHandle {
-
-        @JsonProperty()
-        private final String id;
-
-        private final List<Filter> filters;
-    }
-
-    @AllArgsConstructor
-    @Data
-    @JsonInclude(JsonInclude.Include.NON_NULL)
-    public static class Filter {
-
-        @JsonProperty()
-        private final String id;
-
-        @JsonProperty()
-        private final List<String> subscribers;
-    }
-}
-
-
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java
new file mode 100644 (file)
index 0000000..cde4eac
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 highstreet technologies GmbH
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2021 Pantheon.tech
+ *  Modifications Copyright (C) 2021-2022 Bell Canada
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  ================================================================================
+ *  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 static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateCmHandleQueryParameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration;
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService;
+import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions;
+import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager;
+import org.onap.cps.ncmp.impl.models.RequiredDmiService;
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
+import org.onap.cps.spi.model.ModuleDefinition;
+import org.onap.cps.spi.model.ModuleReference;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class NetworkCmProxyInventoryFacade {
+
+    private final CmHandleRegistrationService cmHandleRegistrationService;
+    private final CmHandleQueryService cmHandleQueryService;
+    private final ParameterizedCmHandleQueryService parameterizedCmHandleQueryService;
+    private final InventoryPersistence inventoryPersistence;
+    private final JsonObjectMapper jsonObjectMapper;
+    private final TrustLevelManager trustLevelManager;
+    private final AlternateIdMatcher alternateIdMatcher;
+
+    /**
+     * Registration of Created, Removed, Updated or Upgraded CM Handles.
+     *
+     * @param dmiPluginRegistration Dmi Plugin Registration details
+     * @return dmiPluginRegistrationResponse
+     */
+    public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(
+        final DmiPluginRegistration dmiPluginRegistration) {
+        return cmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration);
+    }
+
+    /**
+     * Get all cm handle IDs by DMI plugin identifier.
+     *
+     * @param dmiPluginIdentifier DMI plugin identifier
+     * @return collection of cm handle IDs
+     */
+    public Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) {
+        return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier);
+    }
+
+    /**
+     * Get all cm handle IDs by various properties.
+     *
+     * @param cmHandleQueryServiceParameters cm handle query parameters
+     * @return collection of cm handle IDs
+     */
+    public Collection<String> executeParameterizedCmHandleIdSearch(
+        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+        validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES);
+        return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters);
+    }
+
+
+    /**
+     * Retrieve module references for the given cm handle reference.
+     *
+     * @param cmHandleReference cm handle or alternate id identifier
+     * @return a collection of modules names and revisions
+     */
+    public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleReference) {
+        final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference);
+        return inventoryPersistence.getYangResourcesModuleReferences(cmHandleId);
+    }
+
+    /**
+     * Retrieve module definitions for the given cm handle.
+     *
+     * @param cmHandleReference cm handle or alternate id identifier
+     * @return a collection of module definition (moduleName, revision and yang resource content)
+     */
+    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleReference(final String cmHandleReference) {
+        final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference);
+        return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId);
+    }
+
+    /**
+     * Get module definitions for the given parameters.
+     *
+     * @param cmHandleReference  cm handle or alternate id identifier
+     * @param moduleName         module name
+     * @param moduleRevision     the revision of the module
+     * @return list of module definitions (module name, revision, yang resource content)
+     */
+    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleReference,
+                                                                                final String moduleName,
+                                                                                final String moduleRevision) {
+        final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference);
+        return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision);
+    }
+
+    /**
+     * Retrieve cm handles with details for the given query parameters.
+     *
+     * @param cmHandleQueryApiParameters cm handle query parameters
+     * @return cm handles with details
+     */
+    public Collection<NcmpServiceCmHandle> executeCmHandleSearch(
+        final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
+        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
+            cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
+        validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
+        final Collection<YangModelCmHandle> yangModelCmHandles =
+            parameterizedCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters);
+        final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = new ArrayList<>(yangModelCmHandles.size());
+        for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) {
+            final NcmpServiceCmHandle ncmpServiceCmHandle = toNcmpServiceCmHandleWithTrustLevel(yangModelCmHandle);
+            ncmpServiceCmHandles.add(ncmpServiceCmHandle);
+        }
+        return ncmpServiceCmHandles;
+    }
+
+    /**
+     * Retrieve cm handle ids for the given query parameters.
+     *
+     * @param cmHandleQueryApiParameters cm handle query parameters
+     * @return cm handle ids
+     */
+    public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
+        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
+            cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
+        validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
+        return parameterizedCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters);
+    }
+
+    /**
+     * Set the data sync enabled flag, along with the data sync state
+     * based on the data sync enabled boolean for the cm handle id provided.
+     *
+     * @param cmHandleId                 cm handle id
+     * @param dataSyncEnabledTargetValue data sync enabled flag
+     */
+    public void setDataSyncEnabled(final String cmHandleId, final Boolean dataSyncEnabledTargetValue) {
+        cmHandleRegistrationService.setDataSyncEnabled(cmHandleId, dataSyncEnabledTargetValue);
+    }
+
+    /**
+     * Retrieve cm handle details for a given cm handle reference.
+     *
+     * @param cmHandleReference cm handle or alternate identifier
+     * @return cm handle details
+     */
+    public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleReference) {
+        final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference);
+        final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
+        return toNcmpServiceCmHandleWithTrustLevel(yangModelCmHandle);
+    }
+
+    /**
+     * Get cm handle public properties for a given cm handle or alternate id.
+     *
+     * @param cmHandleReference cm handle or alternate identifier
+     * @return cm handle public properties
+     */
+    public Map<String, String> getCmHandlePublicProperties(final String cmHandleReference) {
+        final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference);
+        final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
+        return YangDataConverter.toPropertiesMap(yangModelCmHandle.getPublicProperties());
+    }
+
+    /**
+     * Get cm handle composite state for a given cm handle id.
+     *
+     * @param cmHandleReference cm handle or alternate identifier
+     * @return cm handle state
+     */
+    public CompositeState getCmHandleCompositeState(final String cmHandleReference) {
+        final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference);
+        return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState();
+    }
+
+    private NcmpServiceCmHandle toNcmpServiceCmHandleWithTrustLevel(final YangModelCmHandle yangModelCmHandle) {
+        final NcmpServiceCmHandle ncmpServiceCmHandle = YangDataConverter.toNcmpServiceCmHandle(yangModelCmHandle);
+        final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA);
+        ncmpServiceCmHandle.setCurrentTrustLevel(
+                trustLevelManager.getEffectiveTrustLevel(dmiServiceName, ncmpServiceCmHandle.getCmHandleId()));
+        return ncmpServiceCmHandle;
+    }
+
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -19,7 +19,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR;
 
@@ -31,7 +31,7 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.NcmpResponseStatus;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 
 @Data
 @Builder
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -29,6 +29,9 @@ import lombok.Data;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory;
 
 /**
  * State Model to store state corresponding to the Yang resource dmi-registry model.
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.api.inventory.models;
 
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState.LockReason;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState.LockReason;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational;
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory;
 import org.onap.cps.spi.model.DataNode;
 
 public class CompositeStateBuilder {
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -27,8 +27,8 @@ import java.util.Collections;
 import java.util.List;
 import lombok.Getter;
 import lombok.Setter;
-import org.onap.cps.ncmp.api.impl.exception.DmiRequestException;
-import org.onap.cps.ncmp.api.impl.exception.NcmpException;
+import org.onap.cps.ncmp.api.exceptions.DmiRequestException;
+import org.onap.cps.ncmp.api.exceptions.NcmpException;
 
 /**
  * Dmi Registry request object.
@@ -19,7 +19,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import java.util.Collections;
 import java.util.List;
@@ -33,4 +33,4 @@ public class DmiPluginRegistrationResponse {
     private List<CmHandleRegistrationResponse> updatedCmHandles = Collections.emptyList();
     private List<CmHandleRegistrationResponse> removedCmHandles = Collections.emptyList();
     private List<CmHandleRegistrationResponse> upgradedCmHandles = Collections.emptyList();
-}
\ No newline at end of file
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import com.fasterxml.jackson.annotation.JsonSetter;
 import com.fasterxml.jackson.annotation.Nulls;
@@ -27,8 +27,6 @@ import java.util.Map;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
 import org.springframework.validation.annotation.Validated;
 
 /**
@@ -58,6 +56,9 @@ public class NcmpServiceCmHandle {
     @JsonSetter(nulls = Nulls.AS_EMPTY)
     private TrustLevel registrationTrustLevel;
 
+    @JsonSetter(nulls = Nulls.AS_EMPTY)
+    private TrustLevel currentTrustLevel;
+
     @JsonSetter(nulls = Nulls.AS_EMPTY)
     private String alternateId;
 
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.inventory.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import java.util.Collections;
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils.context;
+package org.onap.cps.ncmp.config;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
@@ -48,4 +48,4 @@ public class CpsApplicationContext implements ApplicationContextAware {
     private static synchronized void setCpsApplicationContext(final ApplicationContext cpsApplicationContext) {
         CpsApplicationContext.applicationContext = cpsApplicationContext;
     }
-}
\ No newline at end of file
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/DmiHttpClientConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/DmiHttpClientConfig.java
new file mode 100644 (file)
index 0000000..8eebb89
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023-2024 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.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Getter
+@Setter
+@Configuration
+@ConfigurationProperties(prefix = "ncmp.dmi.httpclient")
+public class DmiHttpClientConfig {
+
+    private final DataServices dataServices = new DataServices();
+    private final ModelServices modelServices = new ModelServices();
+    private final HealthCheckServices healthCheckServices = new HealthCheckServices();
+
+    @Getter
+    @Setter
+    public static class DataServices extends ServiceConfig {
+        private String connectionProviderName = "dataConnectionPool";
+    }
+
+    @Getter
+    @Setter
+    public static class ModelServices extends ServiceConfig {
+        private String connectionProviderName = "modelConnectionPool";
+    }
+
+    @Getter
+    @Setter
+    public static class HealthCheckServices extends ServiceConfig {
+        private String connectionProviderName = "healthConnectionPool";
+        private int maximumConnectionsTotal = 10;
+        private int pendingAcquireMaxCount = 5;
+    }
+}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.config.kafka;
+package org.onap.cps.ncmp.config;
 
 import io.cloudevents.CloudEvent;
+import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor;
+import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor;
 import java.time.Duration;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.apache.kafka.clients.producer.ProducerConfig;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
 import org.springframework.boot.ssl.SslBundles;
 import org.springframework.context.annotation.Bean;
@@ -52,6 +56,9 @@ public class KafkaConfig<T> {
 
     private final KafkaProperties kafkaProperties;
 
+    @Value("${cps.tracing.enabled:false}")
+    private boolean tracingEnabled;
+
     private static final SslBundles NO_SSL = null;
 
     /**
@@ -64,6 +71,10 @@ public class KafkaConfig<T> {
     public ProducerFactory<String, T> legacyEventProducerFactory() {
         final Map<String, Object> producerConfigProperties = kafkaProperties.buildProducerProperties(NO_SSL);
         producerConfigProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
+        if (tracingEnabled) {
+            producerConfigProperties.put(
+                ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName());
+        }
         return new DefaultKafkaProducerFactory<>(producerConfigProperties);
     }
 
@@ -77,6 +88,10 @@ public class KafkaConfig<T> {
     public ConsumerFactory<String, T> legacyEventConsumerFactory() {
         final Map<String, Object> consumerConfigProperties = kafkaProperties.buildConsumerProperties(NO_SSL);
         consumerConfigProperties.put("spring.deserializer.value.delegate.class", JsonDeserializer.class);
+        if (tracingEnabled) {
+            consumerConfigProperties.put(
+                ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName());
+        }
         return new DefaultKafkaConsumerFactory<>(consumerConfigProperties);
     }
 
@@ -90,6 +105,9 @@ public class KafkaConfig<T> {
     public KafkaTemplate<String, T> legacyEventKafkaTemplate() {
         final KafkaTemplate<String, T> kafkaTemplate = new KafkaTemplate<>(legacyEventProducerFactory());
         kafkaTemplate.setConsumerFactory(legacyEventConsumerFactory());
+        if (tracingEnabled) {
+            kafkaTemplate.setObservationEnabled(true);
+        }
         return kafkaTemplate;
     }
 
@@ -104,6 +122,9 @@ public class KafkaConfig<T> {
                 new ConcurrentKafkaListenerContainerFactory<>();
         containerFactory.setConsumerFactory(legacyEventConsumerFactory());
         containerFactory.getContainerProperties().setAuthExceptionRetryInterval(Duration.ofSeconds(10));
+        if (tracingEnabled) {
+            containerFactory.getContainerProperties().setObservationEnabled(true);
+        }
         return containerFactory;
     }
 
@@ -116,6 +137,10 @@ public class KafkaConfig<T> {
     @Bean
     public ProducerFactory<String, CloudEvent> cloudEventProducerFactory() {
         final Map<String, Object> producerConfigProperties = kafkaProperties.buildProducerProperties(NO_SSL);
+        if (tracingEnabled) {
+            producerConfigProperties.put(
+                ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName());
+        }
         return new DefaultKafkaProducerFactory<>(producerConfigProperties);
     }
 
@@ -128,6 +153,10 @@ public class KafkaConfig<T> {
     @Bean
     public ConsumerFactory<String, CloudEvent> cloudEventConsumerFactory() {
         final Map<String, Object> consumerConfigProperties = kafkaProperties.buildConsumerProperties(NO_SSL);
+        if (tracingEnabled) {
+            consumerConfigProperties.put(
+                ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName());
+        }
         return new DefaultKafkaConsumerFactory<>(consumerConfigProperties);
     }
 
@@ -142,6 +171,9 @@ public class KafkaConfig<T> {
         final KafkaTemplate<String, CloudEvent> kafkaTemplate =
             new KafkaTemplate<>(cloudEventProducerFactory());
         kafkaTemplate.setConsumerFactory(cloudEventConsumerFactory());
+        if (tracingEnabled) {
+            kafkaTemplate.setObservationEnabled(true);
+        }
         return kafkaTemplate;
     }
 
@@ -157,6 +189,9 @@ public class KafkaConfig<T> {
                 new ConcurrentKafkaListenerContainerFactory<>();
         containerFactory.setConsumerFactory(cloudEventConsumerFactory());
         containerFactory.getContainerProperties().setAuthExceptionRetryInterval(Duration.ofSeconds(10));
+        if (tracingEnabled) {
+            containerFactory.getContainerProperties().setObservationEnabled(true);
+        }
         return containerFactory;
     }
 
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/OpenTelemetryConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/OpenTelemetryConfig.java
new file mode 100644 (file)
index 0000000..a6a82b7
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 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.config;
+
+import io.micrometer.observation.ObservationPredicate;
+import io.micrometer.observation.ObservationRegistry;
+import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
+import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
+import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler;
+import io.opentelemetry.sdk.trace.samplers.Sampler;
+import jakarta.annotation.PostConstruct;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.autoconfigure.observation.ObservationRegistryCustomizer;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.server.observation.ServerRequestObservationContext;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+/**
+ * Configuration class for setting up OpenTelemetry tracing in a Spring Boot application.
+ * This class provides beans for OTLP exporters (gRPC and HTTP), a Jaeger remote sampler,
+ * and customizes the ObservationRegistry to exclude certain endpoints from being observed.
+ */
+@Configuration
+public class OpenTelemetryConfig {
+
+    @Value("${spring.application.name:cps-application}")
+    private String serviceId;
+
+    @Value("${cps.tracing.exporter.endpoint:http://onap-otel-collector:4317}")
+    private String tracingExporterEndpointUrl;
+
+    @Value("${cps.tracing.sampler.jaeger_remote.endpoint:http://onap-otel-collector:14250}")
+    private String jaegerRemoteSamplerUrl;
+
+    @Value("${cps.tracing.excluded-observation-names:tasks.scheduled.execution}")
+    private String excludedObservationNamesAsCsv;
+
+    private static final int JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECONDS = 30;
+
+    private List<String> excludedObservationNames;
+
+    /**
+     * Initializes the excludedObservationNames after the bean's properties have been set.
+     * This method is called by the Spring container during bean initialization.
+     */
+    @PostConstruct
+    public void init() {
+        excludedObservationNames = Arrays.stream(excludedObservationNamesAsCsv.split(","))
+                .map(String::trim)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Creates an OTLP Exporter with gRPC protocol.
+     *
+     * @return OtlpGrpcSpanExporter bean if tracing is enabled and the exporter protocol is gRPC
+     */
+    @Bean
+    @ConditionalOnExpression(
+        "${cps.tracing.enabled} && 'grpc'.equals('${cps.tracing.exporter.protocol}')")
+    public OtlpGrpcSpanExporter createOtlpExporterGrpc() {
+        return OtlpGrpcSpanExporter.builder().setEndpoint(tracingExporterEndpointUrl).build();
+    }
+
+    /**
+     * Creates an OTLP Exporter with HTTP protocol.
+     *
+     * @return OtlpHttpSpanExporter bean if tracing is enabled and the exporter protocol is HTTP
+     */
+    @Bean
+    @ConditionalOnExpression(
+        "${cps.tracing.enabled} && 'http'.equals('${cps.tracing.exporter.protocol}')")
+    public OtlpHttpSpanExporter createOtlpExporterHttp() {
+        return OtlpHttpSpanExporter.builder().setEndpoint(tracingExporterEndpointUrl).build();
+    }
+
+    /**
+     * Creates a Jaeger Remote Sampler.
+     *
+     * @return JaegerRemoteSampler bean if tracing is enabled
+     */
+    @Bean
+    @ConditionalOnProperty("cps.tracing.enabled")
+    public JaegerRemoteSampler createJaegerRemoteSampler() {
+        return JaegerRemoteSampler.builder()
+                .setEndpoint(jaegerRemoteSamplerUrl)
+                .setPollingInterval(Duration.ofSeconds(JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECONDS))
+                .setInitialSampler(Sampler.alwaysOff())
+                .setServiceName(serviceId)
+                .build();
+    }
+
+    /**
+     * Customizes the ObservationRegistry to exclude /actuator/** endpoints from being observed.
+     *
+     * @return ObservationRegistryCustomizer bean if tracing is enabled
+     */
+    @Bean
+    @ConditionalOnProperty("cps.tracing.enabled")
+    public ObservationRegistryCustomizer<ObservationRegistry> skipActuatorEndpointsFromObservation() {
+        final PathMatcher pathMatcher = new AntPathMatcher("/");
+        return registry ->
+                registry.observationConfig().observationPredicate(observationPredicate(pathMatcher));
+    }
+
+    private ObservationPredicate observationPredicate(final PathMatcher pathMatcher) {
+        return (observationName, context) -> {
+            if (context instanceof ServerRequestObservationContext observationContext) {
+                return !pathMatcher.match("/actuator/**", observationContext.getCarrier().getRequestURI());
+            } else {
+                return !excludedObservationNames.contains(observationName);
+            }
+        };
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfig.java
new file mode 100644 (file)
index 0000000..0903c67
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Getter
+@Setter
+@Configuration
+@ConfigurationProperties(prefix = "ncmp.policy-executor.httpclient")
+public class PolicyExecutorHttpClientConfig {
+
+    private final AllServices allServices = new AllServices();
+
+    @Getter
+    @Setter
+    public static class AllServices extends ServiceConfig {
+        private String connectionProviderName = "policyExecutorConfig";
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/ServiceConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/ServiceConfig.java
new file mode 100644 (file)
index 0000000..f1fce0c
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.config;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public abstract class ServiceConfig {
+    private String connectionProviderName = "";
+    private int maximumInMemorySizeInMegabytes = 1;
+    private int maximumConnectionsTotal = 1;
+    private int pendingAcquireMaxCount = 1;
+    private Integer connectionTimeoutInSeconds = 1;
+    private long readTimeoutInSeconds = 1;
+    private long writeTimeoutInSeconds = 1;
+}
@@ -18,7 +18,9 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.exception;
+package org.onap.cps.ncmp.exceptions;
+
+import org.onap.cps.ncmp.api.exceptions.NcmpException;
 
 /**
  * NCMP start up exception.
@@ -34,4 +36,4 @@ public class NcmpStartUpException extends NcmpException {
     public NcmpStartUpException(final String message, final String details) {
         super(message, details);
     }
-}
\ No newline at end of file
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.exception;
+package org.onap.cps.ncmp.exceptions;
 
 import java.io.Serial;
+import org.onap.cps.ncmp.api.exceptions.NcmpException;
 
-public class NoAlternateIdParentFoundException extends NcmpException {
+public class NoAlternateIdMatchFoundException extends NcmpException {
 
     @Serial
     private static final long serialVersionUID = -2412915490233422945L;
-    private static final String ALTERNATE_ID_NOT_FOUND = "No matching (parent) cm handle found using alternate ids";
+    private static final String ALTERNATE_ID_NOT_FOUND = "No matching cm handle found using alternate ids";
 
     /**
      * Constructor.
      *
      * @param cpsPath datanode cpsPath
      */
-    public NoAlternateIdParentFoundException(final String cpsPath) {
+    public NoAlternateIdMatchFoundException(final String cpsPath) {
         super(ALTERNATE_ID_NOT_FOUND, String.format("cannot find a datanode with alternate id %s", cpsPath));
     }
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.config.embeddedcache;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.cache;
 
 import com.hazelcast.config.MapConfig;
 import com.hazelcast.map.IMap;
 import java.util.Map;
 import org.onap.cps.cache.HazelcastCacheConfig;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
-public class CmNotificationSubscriptionCacheConfig extends HazelcastCacheConfig {
+public class CmSubscriptionConfig extends HazelcastCacheConfig {
 
     private static final MapConfig cmNotificationSubscriptionCacheMapConfig =
             createMapConfig("cmNotificationSubscriptionCacheMapConfig");
@@ -42,7 +42,7 @@ public class CmNotificationSubscriptionCacheConfig extends HazelcastCacheConfig
      * @return configured map of subscription events.
      */
     @Bean
-    public IMap<String, Map<String, DmiCmNotificationSubscriptionDetails>> cmNotificationSubscriptionCache() {
+    public IMap<String, Map<String, DmiCmSubscriptionDetails>> cmNotificationSubscriptionCache() {
         return createHazelcastInstance("hazelCastInstanceCmNotificationSubscription",
                 cmNotificationSubscriptionCacheMapConfig).getMap("cmNotificationSubscriptionCache");
     }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/DmiCacheHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/cache/DmiCacheHandler.java
new file mode 100644 (file)
index 0000000..b5ab7f6
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.cache;
+
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.PENDING;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.Predicate;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class DmiCacheHandler {
+
+    private final CmSubscriptionPersistenceService cmSubscriptionPersistenceService;
+    private final Map<String, Map<String, DmiCmSubscriptionDetails>> cmNotificationSubscriptionCache;
+    private final InventoryPersistence inventoryPersistence;
+
+    /**
+     * Adds subscription to the subscription cache.
+     *
+     * @param subscriptionId    subscription id
+     * @param predicates        subscription request predicates
+     */
+    public void add(final String subscriptionId, final List<Predicate> predicates) {
+        cmNotificationSubscriptionCache.put(subscriptionId, createDmiSubscriptionsPerDmi(predicates));
+    }
+
+    /**
+     * Adds subscription to the subscription cache.
+     *
+     * @param subscriptionId subscription id
+     * @param dmiCmSubscriptionDetailsPerDmi map of dmi cm notification subscription details per dmi
+     */
+    public void add(final String subscriptionId,
+                    final Map<String, DmiCmSubscriptionDetails>
+                            dmiCmSubscriptionDetailsPerDmi) {
+        cmNotificationSubscriptionCache.put(subscriptionId, dmiCmSubscriptionDetailsPerDmi);
+    }
+
+    /**
+     * Get cm notification subscription cache entry via subscription id.
+     *
+     * @param subscriptionId    subscription id
+     * @return map of dmi cm notification subscriptions per dmi
+     */
+    public Map<String, DmiCmSubscriptionDetails> get(final String subscriptionId) {
+        return cmNotificationSubscriptionCache.get(subscriptionId);
+    }
+
+
+    /**
+     * Remove cache entries with CmNotificationSubscriptionStatus ACCEPTED/REJECTED via subscription id.
+     *
+     * @param subscriptionId subscription id as key in CM notification Subscription cache.
+     */
+    public void removeAcceptedAndRejectedDmiSubscriptionEntries(final String subscriptionId) {
+        final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi =
+                cmNotificationSubscriptionCache.get(subscriptionId);
+        final Map<String, DmiCmSubscriptionDetails> updatedDmiSubscriptionsPerDmi =
+                dmiSubscriptionsPerDmi.entrySet().stream()
+                        .filter(dmiCmNotificationSubscription -> !isAcceptedOrRejected(
+                                dmiCmNotificationSubscription.getValue()))
+                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+        cmNotificationSubscriptionCache.put(subscriptionId, updatedDmiSubscriptionsPerDmi);
+    }
+
+    /**
+     *  Creates map of subscription details per DMI.
+     *
+     * @param predicates    CM Subscription Create Request Predicates
+     * @return              Map of DmiCmNotificationSubscription per DMI plugin
+     */
+    public Map<String, DmiCmSubscriptionDetails> createDmiSubscriptionsPerDmi(
+            final List<Predicate> predicates) {
+        final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi =
+                new HashMap<>();
+        for (final Predicate requestPredicate : predicates) {
+            final List<String> targetFilter = requestPredicate.getTargetFilter();
+            final DatastoreType datastoreType = DatastoreType.fromDatastoreName(
+                    requestPredicate.getScopeFilter().getDatastore().toString());
+            final Set<String> xpaths = new HashSet<>(requestPredicate.getScopeFilter().getXpathFilter());
+            final Map<String, Set<String>> targetCmHandlesByDmiMap = groupTargetCmHandleIdsByDmi(targetFilter);
+            for (final Map.Entry<String, Set<String>> targetCmHandlesByDmi: targetCmHandlesByDmiMap.entrySet()) {
+                final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate =
+                        new DmiCmSubscriptionPredicate(targetCmHandlesByDmi.getValue(),
+                                datastoreType, xpaths);
+                updateDmiSubscriptionDetailsPerDmi(targetCmHandlesByDmi.getKey(),
+                        dmiCmSubscriptionPredicate,
+                        dmiSubscriptionsPerDmi);
+            }
+        }
+        return dmiSubscriptionsPerDmi;
+    }
+
+    /**
+     *  Update status in map of subscription details per DMI.
+     *
+     * @param subscriptionId    String of subscription Id
+     * @param dmiServiceName    String of dmiServiceName
+     * @param status            String of status
+     *
+     */
+    public void updateDmiSubscriptionStatus(final String subscriptionId, final String dmiServiceName,
+                                            final CmSubscriptionStatus status) {
+        final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi =
+                cmNotificationSubscriptionCache.get(subscriptionId);
+        dmiSubscriptionsPerDmi.get(dmiServiceName).setCmSubscriptionStatus(status);
+        cmNotificationSubscriptionCache.put(subscriptionId, dmiSubscriptionsPerDmi);
+    }
+
+    /**
+     *  Persist map of subscription details per DMI.
+     *
+     * @param subscriptionId    String of subscription Id
+     * @param dmiServiceName    String of dmiServiceName
+     *
+     */
+    public void persistIntoDatabasePerDmi(final String subscriptionId, final String dmiServiceName) {
+        final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates =
+                cmNotificationSubscriptionCache.get(subscriptionId).get(dmiServiceName)
+                        .getDmiCmSubscriptionPredicates();
+        for (final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate : dmiCmSubscriptionPredicates) {
+            final DatastoreType datastoreType = dmiCmSubscriptionPredicate.getDatastoreType();
+            final Set<String> cmHandles = dmiCmSubscriptionPredicate.getTargetCmHandleIds();
+            final Set<String> xpaths = dmiCmSubscriptionPredicate.getXpaths();
+
+            for (final String cmHandle: cmHandles) {
+                for (final String xpath: xpaths) {
+                    cmSubscriptionPersistenceService.addCmSubscription(datastoreType, cmHandle,
+                            xpath, subscriptionId);
+                }
+            }
+        }
+    }
+
+    /**
+     *  Remove subscription from database per DMI service name.
+     *
+     * @param subscriptionId    String of subscription id
+     * @param dmiServiceName    String of dmiServiceName
+     *
+     */
+    public void removeFromDatabase(final String subscriptionId, final String dmiServiceName) {
+        final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates =
+                cmNotificationSubscriptionCache.get(subscriptionId).get(dmiServiceName)
+                        .getDmiCmSubscriptionPredicates();
+        for (final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate : dmiCmSubscriptionPredicates) {
+            final DatastoreType datastoreType = dmiCmSubscriptionPredicate.getDatastoreType();
+            final Set<String> cmHandles = dmiCmSubscriptionPredicate.getTargetCmHandleIds();
+            final Set<String> xpaths = dmiCmSubscriptionPredicate.getXpaths();
+
+            for (final String cmHandle: cmHandles) {
+                for (final String xpath: xpaths) {
+                    cmSubscriptionPersistenceService.removeCmSubscription(datastoreType,
+                            cmHandle, xpath, subscriptionId);
+                }
+            }
+        }
+    }
+
+    private void updateDmiSubscriptionDetailsPerDmi(
+            final String dmiServiceName,
+            final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate,
+            final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi) {
+        if (dmiSubscriptionsPerDmi.containsKey(dmiServiceName)) {
+            dmiSubscriptionsPerDmi.get(dmiServiceName)
+                    .getDmiCmSubscriptionPredicates().add(dmiCmSubscriptionPredicate);
+        } else {
+            dmiSubscriptionsPerDmi.put(dmiServiceName,
+                    new DmiCmSubscriptionDetails(
+                            new ArrayList<>(List.of(dmiCmSubscriptionPredicate)),
+                            PENDING));
+        }
+    }
+
+    private Map<String, Set<String>> groupTargetCmHandleIdsByDmi(final List<String> targetCmHandleIds) {
+        final Map<String, Set<String>> targetCmHandlesByDmiServiceNames = new HashMap<>();
+        final Collection<YangModelCmHandle> yangModelCmHandles =
+                inventoryPersistence.getYangModelCmHandles(targetCmHandleIds);
+
+        for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) {
+            final String dmiServiceName = yangModelCmHandle.getDmiServiceName();
+            targetCmHandlesByDmiServiceNames.putIfAbsent(dmiServiceName, new HashSet<>());
+            targetCmHandlesByDmiServiceNames.get(dmiServiceName).add(yangModelCmHandle.getId());
+        }
+        return targetCmHandlesByDmiServiceNames;
+    }
+
+    private boolean isAcceptedOrRejected(final DmiCmSubscriptionDetails dmiCmSubscription) {
+        return dmiCmSubscription.getCmSubscriptionStatus().toString().equals("ACCEPTED")
+                || dmiCmSubscription.getCmSubscriptionStatus().toString().equals("REJECTED");
+    }
+}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.avc;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.cmavc;
 
 import io.cloudevents.CloudEvent;
-import io.cloudevents.core.builder.CloudEventBuilder;
-import java.util.UUID;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
@@ -33,13 +31,13 @@ import org.springframework.kafka.annotation.KafkaListener;
 import org.springframework.stereotype.Component;
 
 /**
- * Listener for AVC events.
+ * Listener for AVC events based on Cm Subscriptions.
  */
 @Component
 @Slf4j
 @RequiredArgsConstructor
 @ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
-public class AvcEventConsumer {
+public class CmAvcEventConsumer {
 
 
     @Value("${app.ncmp.avc.cm-events-topic}")
@@ -50,15 +48,14 @@ public class AvcEventConsumer {
     /**
      * Incoming AvcEvent in the form of Consumer Record.
      *
-     * @param avcEventConsumerRecord Incoming raw consumer record
+     * @param cmAvcEventAsConsumerRecord Incoming raw consumer record
      */
     @KafkaListener(topics = "${app.dmi.cm-events.topic}",
             containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
-    public void consumeAndForward(final ConsumerRecord<String, CloudEvent> avcEventConsumerRecord) {
-        log.debug("Consuming AVC event {} ...", avcEventConsumerRecord.value());
-        final String newEventId = UUID.randomUUID().toString();
-        final CloudEvent outgoingAvcEvent =
-                CloudEventBuilder.from(avcEventConsumerRecord.value()).withId(newEventId).build();
-        eventsPublisher.publishCloudEvent(cmEventsTopicName, newEventId, outgoingAvcEvent);
+    public void consumeAndForward(
+            final ConsumerRecord<String, CloudEvent> cmAvcEventAsConsumerRecord) {
+        final CloudEvent outgoingAvcEvent = cmAvcEventAsConsumerRecord.value();
+        log.debug("Consuming AVC event {} ...", outgoingAvcEvent);
+        eventsPublisher.publishCloudEvent(cmEventsTopicName, outgoingAvcEvent.getId(), outgoingAvcEvent);
     }
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiCmSubscriptionDetailsPerDmiMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiCmSubscriptionDetailsPerDmiMapper.java
new file mode 100644 (file)
index 0000000..423c603
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.dmi;
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.fromDatastoreName;
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.PENDING;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionKey;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DmiCmSubscriptionDetailsPerDmiMapper {
+
+    /**
+     * Maps Dmi Subscription Keys grouped by Dmi Plugin to DmiCmSubscriptionDetails per Dmi plugin.
+     *
+     * @param subscribersPerDmi Details managed by each dmi plugin
+     * @return Grouped Dmi Subscription details per dmi plugin
+     */
+    public Map<String, DmiCmSubscriptionDetails> toDmiCmSubscriptionsPerDmi(
+            final Map<String, Collection<DmiCmSubscriptionKey>> subscribersPerDmi) {
+
+        final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi = new HashMap<>();
+
+        subscribersPerDmi.forEach((dmiPluginName, dmiCmSubscriptionKeys) -> {
+            final Map<DatastoreTypeAndXpath, List<DmiCmSubscriptionKey>> groupedByDatastoreTypeAndXpath =
+                    groupByDatastoreTypeAndXpath(dmiCmSubscriptionKeys);
+
+            final List<DmiCmSubscriptionPredicate> dmiSubscriptionPredicates =
+                    createDmiCmSubscriptionPredicates(groupedByDatastoreTypeAndXpath);
+
+            final DmiCmSubscriptionDetails dmiCmSubscriptionDetails =
+                    new DmiCmSubscriptionDetails(dmiSubscriptionPredicates, PENDING);
+
+            dmiSubscriptionsPerDmi.put(dmiPluginName, dmiCmSubscriptionDetails);
+        });
+
+        return dmiSubscriptionsPerDmi;
+    }
+
+    private static Map<DatastoreTypeAndXpath, List<DmiCmSubscriptionKey>> groupByDatastoreTypeAndXpath(
+            final Collection<DmiCmSubscriptionKey> dmiCmSubscriptionKeys) {
+        return dmiCmSubscriptionKeys.stream().collect(Collectors.groupingBy(
+                datastoreTypeAndXpath -> new DatastoreTypeAndXpath(
+                        fromDatastoreName(datastoreTypeAndXpath.datastoreName()), datastoreTypeAndXpath.xpath())));
+    }
+
+    private static List<DmiCmSubscriptionPredicate> createDmiCmSubscriptionPredicates(
+            final Map<DatastoreTypeAndXpath, List<DmiCmSubscriptionKey>> groupedByDatastoreTypeAndXpath) {
+        final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates = new ArrayList<>();
+
+        for (final Map.Entry<DatastoreTypeAndXpath, List<DmiCmSubscriptionKey>> datastoreTypeXpathGroupEntry :
+                groupedByDatastoreTypeAndXpath.entrySet()) {
+            final DatastoreTypeAndXpath datastoreTypeAndXpath = datastoreTypeXpathGroupEntry.getKey();
+            final Set<String> cmHandleIds = new HashSet<>();
+
+            for (final DmiCmSubscriptionKey dmiCmSubscriptionKey : datastoreTypeXpathGroupEntry.getValue()) {
+                cmHandleIds.add(dmiCmSubscriptionKey.cmHandleId());
+            }
+
+            final Set<String> xpaths = Collections.singleton(datastoreTypeAndXpath.xpath());
+            dmiCmSubscriptionPredicates.add(
+                    new DmiCmSubscriptionPredicate(cmHandleIds, datastoreTypeAndXpath.datastoreType(), xpaths));
+        }
+
+        return dmiCmSubscriptionPredicates;
+    }
+
+
+    private record DatastoreTypeAndXpath(DatastoreType datastoreType, String xpath) { }
+
+}
+
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -27,46 +27,44 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Cmhandle;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Data;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Predicate;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.ScopeFilter;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.CmHandle;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.Data;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.DmiInEvent;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.Predicate;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.ScopeFilter;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
 import org.springframework.stereotype.Component;
 
 @Component
 @RequiredArgsConstructor
-public class CmNotificationSubscriptionDmiInEventMapper {
+public class DmiInEventMapper {
 
     private final InventoryPersistence inventoryPersistence;
 
     /**
      * Mapper to form a request for the DMI Plugin for the Cm Notification Subscription.
      *
-     * @param dmiCmNotificationSubscriptionPredicates Collection of Cm Notification Subscription predicates
-     * @return CmNotificationSubscriptionDmiInEvent to be sent to DMI Plugin
+     * @param dmiCmSubscriptionPredicates Collection of Cm Notification Subscription predicates
+     * @return DmiInEvent to be sent to DMI Plugin
      */
-    public CmNotificationSubscriptionDmiInEvent toCmNotificationSubscriptionDmiInEvent(
-            final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates) {
-        final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent =
-                new CmNotificationSubscriptionDmiInEvent();
+    public DmiInEvent toDmiInEvent(final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) {
+        final DmiInEvent dmiInEvent = new DmiInEvent();
         final Data cmSubscriptionData = new Data();
-        cmSubscriptionData.setPredicates(mapToDmiInEventPredicates(dmiCmNotificationSubscriptionPredicates));
-        cmSubscriptionData.setCmhandles(mapToCmSubscriptionCmhandleWithPrivateProperties(
-                extractUniqueCmHandleIds(dmiCmNotificationSubscriptionPredicates)));
-        cmNotificationSubscriptionDmiInEvent.setData(cmSubscriptionData);
-        return cmNotificationSubscriptionDmiInEvent;
+        cmSubscriptionData.setPredicates(mapToDmiInEventPredicates(dmiCmSubscriptionPredicates));
+        cmSubscriptionData.setCmHandles(mapToCmSubscriptionCmHandleWithPrivateProperties(
+                extractUniqueCmHandleIds(dmiCmSubscriptionPredicates)));
+        dmiInEvent.setData(cmSubscriptionData);
+        return dmiInEvent;
 
     }
 
     private List<Predicate> mapToDmiInEventPredicates(
-            final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates) {
+            final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) {
 
         final List<Predicate> predicates = new ArrayList<>();
 
-        dmiCmNotificationSubscriptionPredicates.forEach(dmiCmNotificationSubscriptionPredicate -> {
+        dmiCmSubscriptionPredicates.forEach(dmiCmNotificationSubscriptionPredicate -> {
             final Predicate predicate = new Predicate();
             final ScopeFilter scopeFilter = new ScopeFilter();
             scopeFilter.setDatastore(ScopeFilter.Datastore.fromValue(
@@ -81,12 +79,12 @@ public class CmNotificationSubscriptionDmiInEventMapper {
 
     }
 
-    private List<Cmhandle> mapToCmSubscriptionCmhandleWithPrivateProperties(final Set<String> cmHandleIds) {
+    private List<CmHandle> mapToCmSubscriptionCmHandleWithPrivateProperties(final Set<String> cmHandleIds) {
 
-        final List<Cmhandle> cmSubscriptionCmHandles = new ArrayList<>();
+        final List<CmHandle> cmSubscriptionCmHandles = new ArrayList<>();
 
         inventoryPersistence.getYangModelCmHandles(cmHandleIds).forEach(yangModelCmHandle -> {
-            final Cmhandle cmhandle = new Cmhandle();
+            final CmHandle cmhandle = new CmHandle();
             final Map<String, String> cmhandleDmiProperties = new LinkedHashMap<>();
             yangModelCmHandle.getDmiProperties()
                     .forEach(dmiProperty -> cmhandleDmiProperties.put(dmiProperty.getName(), dmiProperty.getValue()));
@@ -99,11 +97,10 @@ public class CmNotificationSubscriptionDmiInEventMapper {
 
     }
 
-    private Set<String> extractUniqueCmHandleIds(
-            final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates) {
+    private Set<String> extractUniqueCmHandleIds(final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) {
 
         final Set<String> cmHandleIds = new HashSet<>();
-        dmiCmNotificationSubscriptionPredicates.forEach(dmiCmNotificationSubscriptionPredicate -> cmHandleIds.addAll(
+        dmiCmSubscriptionPredicates.forEach(dmiCmNotificationSubscriptionPredicate -> cmHandleIds.addAll(
                 dmiCmNotificationSubscriptionPredicate.getTargetCmHandleIds()));
         return cmHandleIds;
     }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.producer;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi;
 
 import io.cloudevents.CloudEvent;
 import io.cloudevents.core.builder.CloudEventBuilder;
 import java.net.URI;
 import java.util.UUID;
 import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.events.EventsPublisher;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.DmiInEvent;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Component;
 
 @Component
-@Slf4j
 @RequiredArgsConstructor
 @ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
-public class CmNotificationSubscriptionDmiInEventProducer {
+public class DmiInEventProducer {
 
     private final EventsPublisher<CloudEvent> eventsPublisher;
     private final JsonObjectMapper jsonObjectMapper;
 
-    @Value("${app.ncmp.avc.subscription-forward-topic-prefix}")
-    private String cmNotificationSubscriptionDmiInEventTopic;
+    @Value("${app.ncmp.avc.cm-subscription-dmi-in}")
+    private String dmiInEventTopic;
 
     /**
      * Publish the event to the provided dmi plugin with key as subscription id and the event is in Cloud Event format.
      *
-     * @param subscriptionId                       Cm Subscription Id
-     * @param dmiPluginName                        Dmi Plugin Name
-     * @param eventType                            Type of event
-     * @param cmNotificationSubscriptionDmiInEvent Cm Notification Subscription event for Dmi
+     * @param subscriptionId Cm Subscription Id
+     * @param dmiPluginName  Dmi Plugin Name
+     * @param eventType      Type of event
+     * @param dmiInEvent     Cm Notification Subscription event for Dmi
      */
-    public void publishCmNotificationSubscriptionDmiInEvent(final String subscriptionId, final String dmiPluginName,
-            final String eventType, final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent) {
-        eventsPublisher.publishCloudEvent(cmNotificationSubscriptionDmiInEventTopic, subscriptionId,
-                buildAndGetCmNotificationDmiInEventAsCloudEvent(subscriptionId, dmiPluginName, eventType,
-                        cmNotificationSubscriptionDmiInEvent));
+    public void publishDmiInEvent(final String subscriptionId, final String dmiPluginName,
+            final String eventType, final DmiInEvent dmiInEvent) {
+        eventsPublisher.publishCloudEvent(dmiInEventTopic, subscriptionId,
+                buildAndGetDmiInEventAsCloudEvent(subscriptionId, dmiPluginName, eventType, dmiInEvent));
 
     }
 
-    private CloudEvent buildAndGetCmNotificationDmiInEventAsCloudEvent(final String subscriptionId,
-            final String dmiPluginName, final String eventType,
-            final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent) {
+    private CloudEvent buildAndGetDmiInEventAsCloudEvent(final String subscriptionId,
+            final String dmiPluginName, final String eventType, final DmiInEvent dmiInEvent) {
         return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withType(eventType)
-                .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.dmi.cm.subscription:1.0.0"))
-                .withExtension("correlationid", subscriptionId.concat("#").concat(dmiPluginName))
-                .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionDmiInEvent)).build();
+                       .withSource(URI.create("NCMP"))
+                       .withDataSchema(URI.create("org.onap.ncmp.dmi.cm.subscription:1.0.0"))
+                       .withExtension("correlationid", subscriptionId.concat("#").concat(dmiPluginName))
+                       .withData(jsonObjectMapper.asJsonBytes(dmiInEvent)).build();
     }
 
 
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiOutEventConsumer.java
new file mode 100644 (file)
index 0000000..20c7c7b
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.dmi;
+
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_DATA_SUBSCRIPTION_ACCEPTED;
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_DATA_SUBSCRIPTION_REJECTED;
+import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent;
+
+import io.cloudevents.CloudEvent;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.onap.cps.ncmp.api.NcmpResponseStatus;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp.NcmpOutEventMapper;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp.NcmpOutEventProducer;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.dmi_to_ncmp.Data;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.dmi_to_ncmp.DmiOutEvent;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class DmiOutEventConsumer {
+
+    private final DmiCacheHandler dmiCacheHandler;
+    private final NcmpOutEventProducer ncmpOutEventProducer;
+    private final NcmpOutEventMapper ncmpOutEventMapper;
+
+    private static final String CM_SUBSCRIPTION_CORRELATION_ID_SEPARATOR = "#";
+
+    /**
+     * Consume the Cm Notification Subscription event from the dmi-plugin.
+     *
+     * @param dmiOutEventAsConsumerRecord the event to be consumed
+     */
+    @KafkaListener(topics = "${app.ncmp.avc.cm-subscription-dmi-out}",
+            containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
+    public void consumeDmiOutEvent(final ConsumerRecord<String, CloudEvent> dmiOutEventAsConsumerRecord) {
+        final CloudEvent cloudEvent = dmiOutEventAsConsumerRecord.value();
+        final DmiOutEvent dmiOutEvent = toTargetEvent(cloudEvent, DmiOutEvent.class);
+        final String correlationId = String.valueOf(cloudEvent.getExtension("correlationid"));
+        if (dmiOutEvent != null && correlationId != null) {
+            final String eventType = cloudEvent.getType();
+            handleDmiOutEvent(correlationId, eventType, dmiOutEvent);
+        }
+    }
+
+    private void handleDmiOutEvent(final String correlationId, final String eventType,
+            final DmiOutEvent dmiOutEvent) {
+        final String subscriptionId = correlationId.split(CM_SUBSCRIPTION_CORRELATION_ID_SEPARATOR)[0];
+        final String dmiPluginName = correlationId.split(CM_SUBSCRIPTION_CORRELATION_ID_SEPARATOR)[1];
+
+        if (checkStatusCodeAndMessage(CM_DATA_SUBSCRIPTION_ACCEPTED, dmiOutEvent.getData())) {
+            handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmSubscriptionStatus.ACCEPTED);
+            if (eventType.equals("subscriptionCreateResponse")) {
+                dmiCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName);
+            }
+            if (eventType.equals("subscriptionDeleteResponse")) {
+                dmiCacheHandler.removeFromDatabase(subscriptionId, dmiPluginName);
+            }
+            handleEventsStatusPerDmi(subscriptionId, eventType);
+        }
+
+        if (checkStatusCodeAndMessage(CM_DATA_SUBSCRIPTION_REJECTED, dmiOutEvent.getData())) {
+            handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmSubscriptionStatus.REJECTED);
+            handleEventsStatusPerDmi(subscriptionId, eventType);
+        }
+
+        log.info("Cm Subscription with id : {} handled by the dmi-plugin : {} has the status : {}", subscriptionId,
+                dmiPluginName, dmiOutEvent.getData().getStatusMessage());
+    }
+
+    private void handleCacheStatusPerDmi(final String subscriptionId, final String dmiPluginName,
+            final CmSubscriptionStatus cmSubscriptionStatus) {
+        dmiCacheHandler.updateDmiSubscriptionStatus(subscriptionId, dmiPluginName,
+                cmSubscriptionStatus);
+    }
+
+    private void handleEventsStatusPerDmi(final String subscriptionId, final String eventType) {
+        final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi = dmiCacheHandler.get(subscriptionId);
+        final NcmpOutEvent ncmpOutEvent = ncmpOutEventMapper.toNcmpOutEvent(subscriptionId, dmiSubscriptionsPerDmi);
+        ncmpOutEventProducer.publishNcmpOutEvent(subscriptionId, eventType, ncmpOutEvent, false);
+    }
+
+    private boolean checkStatusCodeAndMessage(final NcmpResponseStatus ncmpResponseStatus,
+            final Data dmiOutData) {
+        return ncmpResponseStatus.getCode().equals(dmiOutData.getStatusCode())
+                       && ncmpResponseStatus.getMessage()
+                                  .equals(dmiOutData.getStatusMessage());
+    }
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.model;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.models;
 
-public enum CmNotificationSubscriptionStatus {
+public enum CmSubscriptionStatus {
 
     ACCEPTED("ACCEPTED"), REJECTED("REJECTED"), PENDING("PENDING");
 
     private final String cmNotificationSubscriptionStatusValue;
 
-    CmNotificationSubscriptionStatus(final String cmNotificationSubscriptionStatusValue) {
+    CmSubscriptionStatus(final String cmNotificationSubscriptionStatusValue) {
         this.cmNotificationSubscriptionStatusValue = cmNotificationSubscriptionStatusValue;
     }
 }
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.model;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.models;
 
 import java.util.List;
 import lombok.AllArgsConstructor;
@@ -28,8 +28,8 @@ import lombok.Setter;
 @Getter
 @Setter
 @AllArgsConstructor
-public class DmiCmNotificationSubscriptionDetails {
+public class DmiCmSubscriptionDetails {
 
-    private List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates;
-    private CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus;
+    private List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates;
+    private CmSubscriptionStatus cmSubscriptionStatus;
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionKey.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionKey.java
new file mode 100644 (file)
index 0000000..edc3c56
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.models;
+
+/**
+ * Key used to find the records to be sent to the DMI plugin.
+ *
+ * @param datastoreName datastore name
+ * @param cmHandleId    cmhandle id
+ * @param xpath         xpath
+ */
+public record DmiCmSubscriptionKey(String datastoreName, String cmHandleId, String xpath) { }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.model;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.models;
 
 import java.util.Set;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.Setter;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
 
 @Getter
 @Setter
 @AllArgsConstructor
-public class DmiCmNotificationSubscriptionPredicate {
+public class DmiCmSubscriptionPredicate {
 
     private Set<String> targetCmHandleIds;
     private DatastoreType datastoreType;
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionTuple.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/models/DmiCmSubscriptionTuple.java
new file mode 100644 (file)
index 0000000..cd4a15a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.models;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Tuple to be used during for to delete usecase.
+ *
+ * @param lastRemainingSubscriptionsPerDmi   subscriptions that are used by only one subscriber grouped per dmi
+ * @param overlappingSubscriptionsPerDmi     subscriptions that are shared by multiple subscribers grouped per dmi
+ */
+public record DmiCmSubscriptionTuple(Map<String, Collection<DmiCmSubscriptionKey>> lastRemainingSubscriptionsPerDmi,
+                                     Map<String, Collection<DmiCmSubscriptionKey>> overlappingSubscriptionsPerDmi) {
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparator.java
new file mode 100644 (file)
index 0000000..d7f15a2
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.ncmp;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class CmSubscriptionComparator {
+
+    private final CmSubscriptionPersistenceService cmSubscriptionPersistenceService;
+
+    /**
+     * Get the new Dmi Predicates for a given predicates list.
+     *
+     * @param existingDmiCmSubscriptionPredicates list of DmiCmNotificationSubscriptionPredicates
+     * @return new list of DmiCmNotificationSubscriptionPredicates
+     */
+    public List<DmiCmSubscriptionPredicate> getNewDmiSubscriptionPredicates(
+            final List<DmiCmSubscriptionPredicate> existingDmiCmSubscriptionPredicates) {
+        final List<DmiCmSubscriptionPredicate> newDmiCmSubscriptionPredicates =
+                new ArrayList<>();
+
+        for (final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate : existingDmiCmSubscriptionPredicates) {
+
+            final Set<String> targetCmHandleIds = new HashSet<>();
+            final Set<String> xpaths = new HashSet<>();
+            final DatastoreType datastoreType = dmiCmSubscriptionPredicate.getDatastoreType();
+
+            for (final String cmHandleId : dmiCmSubscriptionPredicate.getTargetCmHandleIds()) {
+                for (final String xpath : dmiCmSubscriptionPredicate.getXpaths()) {
+                    if (!cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType,
+                            cmHandleId, xpath)) {
+                        xpaths.add(xpath);
+                        targetCmHandleIds.add(cmHandleId);
+
+                    }
+                }
+            }
+
+            populateValidDmiSubscriptionPredicates(targetCmHandleIds, xpaths, datastoreType,
+                    newDmiCmSubscriptionPredicates);
+        }
+        return newDmiCmSubscriptionPredicates;
+    }
+
+    private void populateValidDmiSubscriptionPredicates(final Set<String> targetCmHandleIds,
+            final Set<String> xpaths, final DatastoreType datastoreType,
+            final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) {
+        if (!(targetCmHandleIds.isEmpty() || xpaths.isEmpty())) {
+            final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate =
+                    new DmiCmSubscriptionPredicate(targetCmHandleIds, datastoreType, xpaths);
+            dmiCmSubscriptionPredicates.add(dmiCmSubscriptionPredicate);
+        }
+    }
+
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.service;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp;
 
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent;
+import java.util.List;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.Predicate;
 
-public interface CmNotificationSubscriptionHandlerService {
+public interface CmSubscriptionHandler {
 
     /**
-     * Process cm notification subscription request.
+     * Process cm notification subscription create request.
      *
-     * @param cmNotificationSubscriptionNcmpInEvent CM Notification Subscription event
+     * @param subscriptionId subscription id
+     * @param predicates subscription predicates
      */
-    void processSubscriptionCreateRequest(
-        final CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent);
+    void processSubscriptionCreateRequest(final String subscriptionId, final List<Predicate> predicates);
 
-}
+    /**
+     * Process cm notification subscription delete request.
+     *
+     * @param subscriptionId subscription id
+     */
+    void processSubscriptionDeleteRequest(final String subscriptionId);
+
+}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionHandlerImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionHandlerImpl.java
new file mode 100644 (file)
index 0000000..1cdc7ed
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.ncmp;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiCmSubscriptionDetailsPerDmiMapper;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiInEventMapper;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiInEventProducer;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionKey;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionTuple;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.Predicate;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.DmiInEvent;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CmSubscriptionHandlerImpl implements CmSubscriptionHandler {
+
+    private static final Pattern SUBSCRIPTION_KEY_FROM_XPATH_PATTERN = Pattern.compile(
+            "^/datastores/datastore\\[@name='([^']*)']/cm-handles/cm-handle\\[@id='([^']*)']/"
+                    + "filters/filter\\[@xpath='(.*)']$");
+
+    private final CmSubscriptionPersistenceService cmSubscriptionPersistenceService;
+    private final CmSubscriptionComparator cmSubscriptionComparator;
+    private final NcmpOutEventMapper ncmpOutEventMapper;
+    private final DmiInEventMapper dmiInEventMapper;
+    private final DmiCmSubscriptionDetailsPerDmiMapper dmiCmSubscriptionDetailsPerDmiMapper;
+    private final NcmpOutEventProducer ncmpOutEventProducer;
+    private final DmiInEventProducer dmiInEventProducer;
+    private final DmiCacheHandler dmiCacheHandler;
+    private final InventoryPersistence inventoryPersistence;
+
+    @Override
+    public void processSubscriptionCreateRequest(final String subscriptionId, final List<Predicate> predicates) {
+        if (cmSubscriptionPersistenceService.isUniqueSubscriptionId(subscriptionId)) {
+            dmiCacheHandler.add(subscriptionId, predicates);
+            handleNewCmSubscription(subscriptionId);
+            scheduleNcmpOutEventResponse(subscriptionId, "subscriptionCreateResponse");
+        } else {
+            rejectAndPublishCreateRequest(subscriptionId, predicates);
+        }
+    }
+
+    @Override
+    public void processSubscriptionDeleteRequest(final String subscriptionId) {
+        final Collection<DataNode> subscriptionDataNodes =
+                cmSubscriptionPersistenceService.getAllNodesForSubscriptionId(subscriptionId);
+        final DmiCmSubscriptionTuple dmiCmSubscriptionTuple =
+                getLastRemainingAndOverlappingSubscriptionsPerDmi(subscriptionDataNodes);
+        dmiCacheHandler.add(subscriptionId, mergeDmiCmSubscriptionDetailsPerDmiMaps(dmiCmSubscriptionTuple));
+        if (dmiCmSubscriptionTuple.lastRemainingSubscriptionsPerDmi().isEmpty()) {
+            acceptAndPublishDeleteRequest(subscriptionId);
+        } else {
+            sendSubscriptionDeleteRequestToDmi(subscriptionId,
+                    dmiCmSubscriptionDetailsPerDmiMapper.toDmiCmSubscriptionsPerDmi(
+                            dmiCmSubscriptionTuple.lastRemainingSubscriptionsPerDmi()));
+            scheduleNcmpOutEventResponse(subscriptionId, "subscriptionDeleteResponse");
+        }
+    }
+
+    private Map<String, DmiCmSubscriptionDetails> mergeDmiCmSubscriptionDetailsPerDmiMaps(
+            final DmiCmSubscriptionTuple dmiCmSubscriptionTuple) {
+        final Map<String, DmiCmSubscriptionDetails> lastRemainingDmiSubscriptionsPerDmi =
+                dmiCmSubscriptionDetailsPerDmiMapper.toDmiCmSubscriptionsPerDmi(
+                        dmiCmSubscriptionTuple.lastRemainingSubscriptionsPerDmi());
+        final Map<String, DmiCmSubscriptionDetails> overlappingDmiSubscriptionsPerDmi =
+                dmiCmSubscriptionDetailsPerDmiMapper.toDmiCmSubscriptionsPerDmi(
+                        dmiCmSubscriptionTuple.overlappingSubscriptionsPerDmi());
+        final Map<String, DmiCmSubscriptionDetails> mergedDmiSubscriptionsPerDmi =
+                new HashMap<>(lastRemainingDmiSubscriptionsPerDmi);
+        overlappingDmiSubscriptionsPerDmi.forEach((dmiServiceName, dmiCmSubscriptionDetails) ->
+                mergedDmiSubscriptionsPerDmi.merge(dmiServiceName, dmiCmSubscriptionDetails,
+                        this::mergeDmiCmSubscriptionDetails));
+        return mergedDmiSubscriptionsPerDmi;
+    }
+
+    private DmiCmSubscriptionDetails mergeDmiCmSubscriptionDetails(
+            final DmiCmSubscriptionDetails dmiCmSubscriptionDetails,
+            final DmiCmSubscriptionDetails otherDmiCmSubscriptionDetails) {
+        final List<DmiCmSubscriptionPredicate> mergedDmiCmSubscriptionPredicates =
+                new ArrayList<>(dmiCmSubscriptionDetails.getDmiCmSubscriptionPredicates());
+        mergedDmiCmSubscriptionPredicates.addAll(otherDmiCmSubscriptionDetails.getDmiCmSubscriptionPredicates());
+        return new DmiCmSubscriptionDetails(mergedDmiCmSubscriptionPredicates, CmSubscriptionStatus.PENDING);
+    }
+
+    private void scheduleNcmpOutEventResponse(final String subscriptionId, final String eventType) {
+        ncmpOutEventProducer.publishNcmpOutEvent(subscriptionId, eventType, null, true);
+    }
+
+    private void rejectAndPublishCreateRequest(final String subscriptionId, final List<Predicate> predicates) {
+        final Set<String> subscriptionTargetFilters =
+                predicates.stream().flatMap(predicate -> predicate.getTargetFilter().stream())
+                        .collect(Collectors.toSet());
+        final NcmpOutEvent ncmpOutEvent = ncmpOutEventMapper.toNcmpOutEventForRejectedRequest(subscriptionId,
+                new ArrayList<>(subscriptionTargetFilters));
+        ncmpOutEventProducer.publishNcmpOutEvent(subscriptionId, "subscriptionCreateResponse", ncmpOutEvent, false);
+    }
+
+    private void acceptAndPublishDeleteRequest(final String subscriptionId) {
+        final Set<String> dmiServiceNames = dmiCacheHandler.get(subscriptionId).keySet();
+        for (final String dmiServiceName : dmiServiceNames) {
+            dmiCacheHandler.updateDmiSubscriptionStatus(subscriptionId, dmiServiceName,
+                    CmSubscriptionStatus.ACCEPTED);
+            dmiCacheHandler.removeFromDatabase(subscriptionId, dmiServiceName);
+        }
+        final NcmpOutEvent ncmpOutEvent = ncmpOutEventMapper.toNcmpOutEvent(subscriptionId,
+                dmiCacheHandler.get(subscriptionId));
+        ncmpOutEventProducer.publishNcmpOutEvent(subscriptionId, "subscriptionDeleteResponse", ncmpOutEvent,
+                false);
+    }
+
+    private void handleNewCmSubscription(final String subscriptionId) {
+        final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi =
+                dmiCacheHandler.get(subscriptionId);
+        dmiSubscriptionsPerDmi.forEach((dmiPluginName, dmiSubscriptionDetails) -> {
+            final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates =
+                    cmSubscriptionComparator.getNewDmiSubscriptionPredicates(
+                            dmiSubscriptionDetails.getDmiCmSubscriptionPredicates());
+
+            if (dmiCmSubscriptionPredicates.isEmpty()) {
+                acceptAndPersistCmSubscriptionPerDmi(subscriptionId, dmiPluginName);
+            } else {
+                publishDmiInEventPerDmi(subscriptionId, dmiPluginName, dmiCmSubscriptionPredicates);
+            }
+        });
+    }
+
+    private void publishDmiInEventPerDmi(final String subscriptionId, final String dmiPluginName,
+                                         final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) {
+        final DmiInEvent dmiInEvent = dmiInEventMapper.toDmiInEvent(dmiCmSubscriptionPredicates);
+        dmiInEventProducer.publishDmiInEvent(subscriptionId, dmiPluginName,
+                "subscriptionCreateRequest", dmiInEvent);
+    }
+
+    private void acceptAndPersistCmSubscriptionPerDmi(final String subscriptionId, final String dmiPluginName) {
+        dmiCacheHandler.updateDmiSubscriptionStatus(subscriptionId, dmiPluginName,
+                CmSubscriptionStatus.ACCEPTED);
+        dmiCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName);
+    }
+
+    private void sendSubscriptionDeleteRequestToDmi(final String subscriptionId,
+                                                    final Map<String, DmiCmSubscriptionDetails>
+                                                            dmiCmSubscriptionsPerDmi) {
+        dmiCmSubscriptionsPerDmi.forEach((dmiPluginName, dmiCmSubscriptionDetails) -> {
+            final DmiInEvent dmiInEvent =
+                    dmiInEventMapper.toDmiInEvent(
+                            dmiCmSubscriptionDetails.getDmiCmSubscriptionPredicates());
+            dmiInEventProducer.publishDmiInEvent(subscriptionId,
+                    dmiPluginName, "subscriptionDeleteRequest", dmiInEvent);
+        });
+    }
+
+
+    private DmiCmSubscriptionTuple getLastRemainingAndOverlappingSubscriptionsPerDmi(
+            final Collection<DataNode> subscriptionNodes) {
+        final Map<String, Collection<DmiCmSubscriptionKey>> lastRemainingSubscriptionsPerDmi = new HashMap<>();
+        final Map<String, Collection<DmiCmSubscriptionKey>> overlappingSubscriptionsPerDmi = new HashMap<>();
+
+        for (final DataNode subscriptionNode : subscriptionNodes) {
+            final DmiCmSubscriptionKey dmiCmSubscriptionKey = extractCmSubscriptionKey(subscriptionNode.getXpath());
+            final String dmiServiceName = inventoryPersistence.getYangModelCmHandle(
+                    dmiCmSubscriptionKey.cmHandleId()).getDmiServiceName();
+            final List<String> subscribers = (List<String>) subscriptionNode.getLeaves().get("subscriptionIds");
+            populateDmiCmSubscriptionTuple(subscribers, overlappingSubscriptionsPerDmi,
+                    lastRemainingSubscriptionsPerDmi, dmiServiceName, dmiCmSubscriptionKey);
+        }
+        return new DmiCmSubscriptionTuple(lastRemainingSubscriptionsPerDmi, overlappingSubscriptionsPerDmi);
+    }
+
+    private static void populateDmiCmSubscriptionTuple(final List<String> subscribers,
+                                                       final Map<String, Collection<DmiCmSubscriptionKey>>
+                                                               overlappingSubscriptionsPerDmi,
+                                                       final Map<String, Collection<DmiCmSubscriptionKey>>
+                                                               lastRemainingSubscriptionsPerDmi,
+                                                       final String dmiServiceName,
+                                                       final DmiCmSubscriptionKey dmiCmSubscriptionKey) {
+        final Map<String, Collection<DmiCmSubscriptionKey>> targetMap =
+                subscribers.size() > 1 ? overlappingSubscriptionsPerDmi : lastRemainingSubscriptionsPerDmi;
+        targetMap.computeIfAbsent(dmiServiceName, dmiName -> new HashSet<>()).add(dmiCmSubscriptionKey);
+    }
+
+    private DmiCmSubscriptionKey extractCmSubscriptionKey(final String xpath) {
+        final Matcher matcher = SUBSCRIPTION_KEY_FROM_XPATH_PATTERN.matcher(xpath);
+        if (matcher.find()) {
+            final String datastoreName = matcher.group(1);
+            final String cmHandleId = matcher.group(2);
+            final String filterXpath = matcher.group(3);
+            return new DmiCmSubscriptionKey(datastoreName, cmHandleId, filterXpath);
+        }
+        throw new IllegalArgumentException("DataNode xpath does not represent a subscription key");
+    }
+
+}
\ No newline at end of file
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp;
 
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent;
+import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent;
 
 import io.cloudevents.CloudEvent;
+import java.util.List;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionHandlerService;
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent;
-import org.springframework.beans.factory.annotation.Value;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.NcmpInEvent;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.Predicate;
 import org.springframework.kafka.annotation.KafkaListener;
 import org.springframework.stereotype.Component;
 
 @Component
 @Slf4j
 @RequiredArgsConstructor
-public class CmNotificationSubscriptionNcmpInEventConsumer {
+public class NcmpInEventConsumer {
 
-    private final CmNotificationSubscriptionHandlerService cmNotificationSubscriptionHandlerService;
-
-    @Value("${notification.enabled:true}")
-    private boolean notificationFeatureEnabled;
+    private final CmSubscriptionHandler cmSubscriptionHandler;
 
     /**
      * Consume the specified event.
      *
-     * @param subscriptionEventConsumerRecord the event to be consumed
+     * @param ncmpInEventAsConsumerRecord the event to be consumed
      */
-    @KafkaListener(topics = "${app.ncmp.avc.subscription-topic}",
+    @KafkaListener(topics = "${app.ncmp.avc.cm-subscription-ncmp-in}",
             containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
-    public void consumeSubscriptionEvent(final ConsumerRecord<String, CloudEvent> subscriptionEventConsumerRecord) {
-        final CloudEvent cloudEvent = subscriptionEventConsumerRecord.value();
-        final CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent =
-                toTargetEvent(cloudEvent, CmNotificationSubscriptionNcmpInEvent.class);
+    public void consumeSubscriptionEvent(final ConsumerRecord<String, CloudEvent> ncmpInEventAsConsumerRecord) {
+        final CloudEvent cloudEvent = ncmpInEventAsConsumerRecord.value();
+        final NcmpInEvent ncmpInEvent =
+                toTargetEvent(cloudEvent, NcmpInEvent.class);
         log.info("Subscription with name {} to be mapped to hazelcast object...",
-                cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId());
+                ncmpInEvent.getData().getSubscriptionId());
 
-        final String subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId();
+        final String subscriptionId = ncmpInEvent.getData().getSubscriptionId();
+        final List<Predicate> predicates = ncmpInEvent.getData().getPredicates();
         if ("subscriptionCreateRequest".equals(cloudEvent.getType())) {
-            log.info("Subscription for source {} with subscription id {} ...", cloudEvent.getSource(), subscriptionId);
-            cmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest(
-                cmNotificationSubscriptionNcmpInEvent);
+            log.info("Subscription create request for source {} with subscription id {} ...",
+                    cloudEvent.getSource(), subscriptionId);
+            cmSubscriptionHandler.processSubscriptionCreateRequest(subscriptionId, predicates);
+        }
+        if ("subscriptionDeleteRequest".equals(cloudEvent.getType())) {
+            log.info("Subscription delete request for source {} with subscription id {} ...",
+                    cloudEvent.getSource(), subscriptionId);
+            cmSubscriptionHandler.processSubscriptionDeleteRequest(subscriptionId);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventMapper.java
new file mode 100644 (file)
index 0000000..afff9d1
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.ncmp;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.Data;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class NcmpOutEventMapper {
+
+    /**
+     * Mapper to form a response for the client for the Cm Notification Subscription.
+     *
+     * @param subscriptionId                          Cm Notification Subscription Id
+     * @param dmiSubscriptionsPerDmi contains CmNotificationSubscriptionDetails per dmi plugin
+     * @return CmNotificationSubscriptionNcmpOutEvent to sent back to the client
+     */
+    public NcmpOutEvent toNcmpOutEvent(final String subscriptionId,
+            final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi) {
+
+        final NcmpOutEvent ncmpOutEvent = new NcmpOutEvent();
+        final Data cmSubscriptionData = new Data();
+        cmSubscriptionData.setSubscriptionId(subscriptionId);
+        populateNcmpOutEventWithCmHandleIds(dmiSubscriptionsPerDmi,
+                cmSubscriptionData);
+        ncmpOutEvent.setData(cmSubscriptionData);
+
+        return ncmpOutEvent;
+    }
+
+    /**
+     * Mapper to form a rejected response for the client for the Cm Notification Subscription Request.
+     *
+     * @param subscriptionId subscription id
+     * @param rejectedTargetFilters list of rejected target filters for the subscription request
+     * @return to sent back to the client
+     */
+    public NcmpOutEvent toNcmpOutEventForRejectedRequest(final String subscriptionId,
+            final List<String> rejectedTargetFilters) {
+        final NcmpOutEvent ncmpOutEvent = new NcmpOutEvent();
+        final Data cmSubscriptionData = new Data();
+        cmSubscriptionData.setSubscriptionId(subscriptionId);
+        cmSubscriptionData.setRejectedTargets(rejectedTargetFilters);
+        ncmpOutEvent.setData(cmSubscriptionData);
+        return ncmpOutEvent;
+    }
+
+    private void populateNcmpOutEventWithCmHandleIds(
+            final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi,
+            final Data cmSubscriptionData) {
+
+        final Collection<String> acceptedCmHandleIds = new HashSet<>();
+        final Collection<String> pendingCmHandleIds = new HashSet<>();
+        final Collection<String> rejectedCmHandleIds = new HashSet<>();
+
+        dmiSubscriptionsPerDmi.forEach((dmiPluginName, dmiSubscriptionDetails) -> {
+            final CmSubscriptionStatus cmSubscriptionStatus =
+                    dmiSubscriptionDetails.getCmSubscriptionStatus();
+            final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates =
+                    dmiSubscriptionDetails.getDmiCmSubscriptionPredicates();
+
+            switch (cmSubscriptionStatus) {
+                case ACCEPTED -> acceptedCmHandleIds.addAll(
+                        extractCmHandleIds(dmiCmSubscriptionPredicates));
+                case PENDING -> pendingCmHandleIds.addAll(extractCmHandleIds(dmiCmSubscriptionPredicates));
+                default -> rejectedCmHandleIds.addAll(extractCmHandleIds(dmiCmSubscriptionPredicates));
+            }
+        });
+
+        cmSubscriptionData.setAcceptedTargets(acceptedCmHandleIds);
+        cmSubscriptionData.setPendingTargets(pendingCmHandleIds);
+        cmSubscriptionData.setRejectedTargets(rejectedCmHandleIds);
+
+    }
+
+    private List<String> extractCmHandleIds(
+            final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) {
+        final List<String> cmHandleIds = new ArrayList<>();
+        dmiCmSubscriptionPredicates.forEach(dmiSubscriptionPredicate -> cmHandleIds.addAll(
+                dmiSubscriptionPredicate.getTargetCmHandleIds()));
+
+        return cmHandleIds;
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventProducer.java
new file mode 100644 (file)
index 0000000..3371d59
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.ncmp;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+import java.net.URI;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.events.EventsPublisher;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
+public class NcmpOutEventProducer {
+
+    @Value("${app.ncmp.avc.cm-subscription-ncmp-out}")
+    private String ncmpOutEventTopic;
+
+    @Value("${ncmp.timers.subscription-forwarding.dmi-response-timeout-ms}")
+    private Integer dmiOutEventTimeoutInMs;
+
+    private final EventsPublisher<CloudEvent> eventsPublisher;
+    private final JsonObjectMapper jsonObjectMapper;
+    private final NcmpOutEventMapper ncmpOutEventMapper;
+    private final DmiCacheHandler dmiCacheHandler;
+    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+    private static final Map<String, ScheduledFuture<?>> scheduledTasksPerSubscriptionIdAndEventType =
+            new ConcurrentHashMap<>();
+
+    /**
+     * Publish the event to the client who requested the subscription with key as subscription id and event is Cloud
+     * Event compliant.
+     *
+     * @param subscriptionId   Cm Subscription Id
+     * @param eventType        Type of event
+     * @param ncmpOutEvent     Cm Notification Subscription Event for the
+     *                         client
+     * @param isScheduledEvent Determines if the event is to be scheduled
+     *                         or published now
+     */
+    public void publishNcmpOutEvent(final String subscriptionId, final String eventType,
+            final NcmpOutEvent ncmpOutEvent, final boolean isScheduledEvent) {
+
+        final String taskKey = subscriptionId.concat(eventType);
+
+        if (isScheduledEvent && !scheduledTasksPerSubscriptionIdAndEventType.containsKey(taskKey)) {
+            final ScheduledFuture<?> scheduledFuture = scheduleAndPublishNcmpOutEvent(subscriptionId, eventType);
+            scheduledTasksPerSubscriptionIdAndEventType.putIfAbsent(taskKey, scheduledFuture);
+            log.debug("Scheduled the Cm Subscription Event for subscriptionId : {} and eventType : {}", subscriptionId,
+                    eventType);
+        } else {
+            cancelScheduledTask(taskKey);
+            if (ncmpOutEvent != null) {
+                publishNcmpOutEventNow(subscriptionId, eventType, ncmpOutEvent);
+                log.debug("Published Cm Subscription Event on demand for subscriptionId : {} and eventType : {}",
+                        subscriptionId, eventType);
+            }
+        }
+    }
+
+    private ScheduledFuture<?> scheduleAndPublishNcmpOutEvent(final String subscriptionId, final String eventType) {
+        final NcmpOutEventPublishingTask ncmpOutEventPublishingTask =
+                new NcmpOutEventPublishingTask(ncmpOutEventTopic, subscriptionId, eventType, eventsPublisher,
+                        jsonObjectMapper, ncmpOutEventMapper, dmiCacheHandler);
+        return scheduledExecutorService.schedule(ncmpOutEventPublishingTask, dmiOutEventTimeoutInMs,
+                TimeUnit.MILLISECONDS);
+    }
+
+    private void cancelScheduledTask(final String taskKey) {
+
+        final ScheduledFuture<?> scheduledFuture = scheduledTasksPerSubscriptionIdAndEventType.get(taskKey);
+        if (scheduledFuture != null) {
+            scheduledFuture.cancel(true);
+            scheduledTasksPerSubscriptionIdAndEventType.remove(taskKey);
+        }
+
+    }
+
+
+    private void publishNcmpOutEventNow(final String subscriptionId, final String eventType,
+            final NcmpOutEvent ncmpOutEvent) {
+        final CloudEvent ncmpOutEventAsCloudEvent =
+                buildAndGetNcmpOutEventAsCloudEvent(jsonObjectMapper, subscriptionId, eventType, ncmpOutEvent);
+        eventsPublisher.publishCloudEvent(ncmpOutEventTopic, subscriptionId, ncmpOutEventAsCloudEvent);
+        dmiCacheHandler.removeAcceptedAndRejectedDmiSubscriptionEntries(subscriptionId);
+    }
+
+    /**
+     * Get an NCMP out event as cloud event.
+     *
+     * @param jsonObjectMapper JSON object mapper
+     * @param subscriptionId   subscription id
+     * @param eventType        event type
+     * @param ncmpOutEvent     cm notification subscription NCMP out event
+     * @return cm notification subscription NCMP out event as cloud event
+     */
+    public static CloudEvent buildAndGetNcmpOutEventAsCloudEvent(final JsonObjectMapper jsonObjectMapper,
+            final String subscriptionId, final String eventType, final NcmpOutEvent ncmpOutEvent) {
+
+        return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withType(eventType)
+                       .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.cm.subscription:1.0.0"))
+                       .withExtension("correlationid", subscriptionId)
+                       .withData(jsonObjectMapper.asJsonBytes(ncmpOutEvent)).build();
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventPublishingTask.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventPublishingTask.java
new file mode 100644 (file)
index 0000000..f8f253d
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.ncmp;
+
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp.NcmpOutEventProducer.buildAndGetNcmpOutEventAsCloudEvent;
+
+import io.cloudevents.CloudEvent;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.events.EventsPublisher;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails;
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent;
+import org.onap.cps.utils.JsonObjectMapper;
+
+@Slf4j
+@RequiredArgsConstructor
+public class NcmpOutEventPublishingTask implements Runnable {
+
+    private final String topicName;
+    private final String subscriptionId;
+    private final String eventType;
+    private final EventsPublisher<CloudEvent> eventsPublisher;
+    private final JsonObjectMapper jsonObjectMapper;
+    private final NcmpOutEventMapper ncmpOutEventMapper;
+    private final DmiCacheHandler dmiCacheHandler;
+
+    /**
+     * Delegating the responsibility of publishing NcmpOutEvent as a separate task which will
+     * be called after a specified delay.
+     */
+    @Override
+    public void run() {
+        final Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi =
+                dmiCacheHandler.get(subscriptionId);
+        final NcmpOutEvent ncmpOutEvent = ncmpOutEventMapper.toNcmpOutEvent(subscriptionId,
+                dmiSubscriptionsPerDmi);
+        eventsPublisher.publishCloudEvent(topicName, subscriptionId,
+                buildAndGetNcmpOutEventAsCloudEvent(jsonObjectMapper, subscriptionId, eventType,
+                        ncmpOutEvent));
+        dmiCacheHandler.removeAcceptedAndRejectedDmiSubscriptionEntries(subscriptionId);
+    }
+}
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -18,8 +19,9 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.service;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.utils;
 
+import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
 
 import java.io.Serializable;
@@ -32,7 +34,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsQueryService;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.JsonObjectMapper;
@@ -41,14 +43,21 @@ import org.springframework.stereotype.Service;
 @Slf4j
 @Service
 @RequiredArgsConstructor
-public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotificationSubscriptionPersistenceService {
+public class CmSubscriptionPersistenceService {
+
+    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
+    private static final String CM_SUBSCRIPTIONS_ANCHOR_NAME = "cm-data-subscriptions";
 
     private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions";
     private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE = """
-            /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']/filters
+            /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']
             """.trim();
+    private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE =
+            CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE + "/filters";
+
     private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH =
-            CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE + "/filter[@xpath='%s']";
+            CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE + "/filter[@xpath='%s']";
+
 
     private static final String CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID = """
             //filter/subscriptionIds[text()='%s']
@@ -58,22 +67,40 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif
     private final CpsQueryService cpsQueryService;
     private final CpsDataService cpsDataService;
 
-    @Override
-    public boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
-                                                       final String xpath) {
-        return !getOngoingCmNotificationSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
+    /**
+     * Check if we have an ongoing cm subscription based on the parameters.
+     *
+     * @param datastoreType the susbcription target datastore type
+     * @param cmHandleId    the id of the cm handle for the susbcription
+     * @param xpath         the target xpath
+     * @return true for ongoing cmsubscription , otherwise false
+     */
+    public boolean isOngoingCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
+            final String xpath) {
+        return !getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
     }
 
-    @Override
+    /**
+     * Check if the subscription ID is unique against ongoing subscriptions.
+     *
+     * @param subscriptionId subscription ID
+     * @return true if subscriptionId is not used in active subscriptions, otherwise false
+     */
     public boolean isUniqueSubscriptionId(final String subscriptionId) {
         return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
-                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId),
-                OMIT_DESCENDANTS).isEmpty();
+                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId), OMIT_DESCENDANTS).isEmpty();
     }
 
-    @Override
-    public Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType,
-                                                                      final String cmHandleId, final String xpath) {
+    /**
+     * Get all ongoing cm notification subscription based on the parameters.
+     *
+     * @param datastoreType the susbcription target datastore type
+     * @param cmHandleId    the id of the cm handle for the susbcription
+     * @param xpath         the target xpath
+     * @return collection of subscription ids of ongoing cm notification subscription
+     */
+    public Collection<String> getOngoingCmSubscriptionIds(final DatastoreType datastoreType,
+            final String cmHandleId, final String xpath) {
 
         final String isOngoingCmSubscriptionCpsPathQuery =
                 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
@@ -87,31 +114,44 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif
         return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
     }
 
-    @Override
-    public void addCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
-                                              final String xpath, final String subscriptionId) {
-        final Collection<String> subscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
-                cmHandleId, xpath);
+    /**
+     * Add cm notification subscription.
+     *
+     * @param datastoreType     the susbcription target datastore type
+     * @param cmHandleId        the id of the cm handle for the susbcription
+     * @param xpath             the target xpath
+     * @param newSubscriptionId subscription id to be added
+     */
+    public void addCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
+            final String xpath, final String newSubscriptionId) {
+        final Collection<String> subscriptionIds =
+                getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath);
         if (subscriptionIds.isEmpty()) {
-            addFirstSubscriptionForDatastoreCmHandleAndXpath(datastoreType, cmHandleId, xpath, subscriptionId);
-        } else if (!subscriptionIds.contains(subscriptionId)) {
-            subscriptionIds.add(subscriptionId);
+            addFirstSubscriptionForDatastoreCmHandleAndXpath(datastoreType, cmHandleId, xpath, newSubscriptionId);
+        } else if (!subscriptionIds.contains(newSubscriptionId)) {
+            subscriptionIds.add(newSubscriptionId);
             saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
         }
     }
 
-    @Override
-    public void removeCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
-                                                 final String xpath, final String subscriptionId) {
-        final Collection<String> subscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
-                cmHandleId, xpath);
+    /**
+     * Remove cm notification Subscription.
+     *
+     * @param datastoreType  the susbcription target datastore type
+     * @param cmHandleId     the id of the cm handle for the susbcription
+     * @param xpath          the target xpath
+     * @param subscriptionId subscription id to remove
+     */
+    public void removeCmSubscription(final DatastoreType datastoreType, final String cmHandleId,
+            final String xpath, final String subscriptionId) {
+        final Collection<String> subscriptionIds =
+                getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath);
         if (subscriptionIds.remove(subscriptionId)) {
-            if (isOngoingCmNotificationSubscription(datastoreType, cmHandleId, xpath)) {
-                saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
-                log.info("There are subscribers left for the following cps path {} :",
-                        CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
-                                datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
-            } else {
+            saveSubscriptionDetails(datastoreType, cmHandleId, xpath, subscriptionIds);
+            log.info("There are subscribers left for the following cps path {} :",
+                    CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
+                            datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
+            if (subscriptionIds.isEmpty()) {
                 log.info("No subscribers left for the following cps path {} :",
                         CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
                                 datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)));
@@ -120,49 +160,74 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif
         }
     }
 
+    /**
+     * Retrieve all existing dataNodes for given subscription id.
+     *
+     * @param subscriptionId  subscription id
+     * @return collection of DataNodes
+     */
+    public Collection<DataNode> getAllNodesForSubscriptionId(final String subscriptionId) {
+        return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted(subscriptionId),
+                OMIT_DESCENDANTS);
+    }
+
     private void deleteListOfSubscriptionsFor(final DatastoreType datastoreType, final String cmHandleId,
-                                              final String xpath) {
+            final String xpath) {
         cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
                 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(
                         datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)),
                 OffsetDateTime.now());
+        final Collection<DataNode> existingFiltersForCmHandle =
+                cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
+                                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
+                                        datastoreType.getDatastoreName(), cmHandleId),
+                                DIRECT_CHILDREN_ONLY).iterator().next()
+                        .getChildDataNodes();
+        if (existingFiltersForCmHandle.isEmpty()) {
+            removeCmHandleFromDatastore(datastoreType.getDatastoreName(), cmHandleId);
+        }
+    }
+
+    private void removeCmHandleFromDatastore(final String datastoreName, final String cmHandleId) {
+        cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, cmHandleId),
+                OffsetDateTime.now());
     }
 
     private boolean isFirstSubscriptionForCmHandle(final DatastoreType datastoreType, final String cmHandleId) {
         return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
-                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(
-                        datastoreType.getDatastoreName(), cmHandleId),
-                OMIT_DESCENDANTS).isEmpty();
+                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
+                        datastoreType.getDatastoreName(), cmHandleId), OMIT_DESCENDANTS).isEmpty();
     }
 
     private void addFirstSubscriptionForDatastoreCmHandleAndXpath(final DatastoreType datastoreType,
-                                                                  final String cmHandleId,
-                                                                  final String xpath,
-                                                                  final String subscriptionId) {
+            final String cmHandleId, final String xpath, final String subscriptionId) {
         final Collection<String> newSubscriptionList = Collections.singletonList(subscriptionId);
         final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, newSubscriptionList);
         if (isFirstSubscriptionForCmHandle(datastoreType, cmHandleId)) {
-            final String parentXpath = "/datastores/datastore[@name='%s']/cm-handles"
-                    .formatted(datastoreType.getDatastoreName());
-            final String subscriptionAsJson = String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":%s}]}",
-                    cmHandleId, subscriptionDetailsAsJson);
+            final String parentXpath =
+                    "/datastores/datastore[@name='%s']/cm-handles".formatted(datastoreType.getDatastoreName());
+            final String subscriptionAsJson =
+                    String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":%s}]}", cmHandleId,
+                            subscriptionDetailsAsJson);
             cpsDataService.saveData(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, subscriptionAsJson,
                     OffsetDateTime.now(), ContentType.JSON);
         } else {
             cpsDataService.saveListElements(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
-                    CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(
-                            datastoreType.getDatastoreName(), cmHandleId),
-                    subscriptionDetailsAsJson, OffsetDateTime.now());
+                    CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
+                            datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson,
+                    OffsetDateTime.now(), ContentType.JSON);
         }
     }
 
-    private void saveSubscriptionDetails(final DatastoreType datastoreType, final String cmHandleId,
-                                         final String xpath,
-                                         final  Collection<String> subscriptionIds) {
+    private void saveSubscriptionDetails(final DatastoreType datastoreType, final String cmHandleId, final String xpath,
+            final Collection<String> subscriptionIds) {
         final String subscriptionDetailsAsJson = getSubscriptionDetailsAsJson(xpath, subscriptionIds);
         cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
-                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(
-                        datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, OffsetDateTime.now());
+                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(
+                        datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, OffsetDateTime.now(),
+                ContentType.JSON);
     }
 
     private String getSubscriptionDetailsAsJson(final String xpath, final Collection<String> subscriptionIds) {
@@ -174,4 +239,6 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif
     private static String escapeQuotesByDoublingThem(final String inputXpath) {
         return inputXpath.replace("'", "''");
     }
+
 }
+
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java
new file mode 100644 (file)
index 0000000..301b819
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications 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.impl.data;
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL;
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING;
+import static org.onap.cps.ncmp.api.data.models.OperationType.READ;
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA;
+
+import io.micrometer.core.annotation.Timed;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.NcmpResponseStatus;
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress;
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest;
+import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException;
+import org.onap.cps.ncmp.impl.data.models.DmiDataOperation;
+import org.onap.cps.ncmp.impl.data.models.DmiDataOperationRequest;
+import org.onap.cps.ncmp.impl.data.models.DmiOperationCmHandle;
+import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor;
+import org.onap.cps.ncmp.impl.data.utils.DmiDataOperationsHelper;
+import org.onap.cps.ncmp.impl.dmi.DmiProperties;
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.models.DmiRequestBody;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.onap.cps.spi.exceptions.CpsException;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * Operations class for DMI data.
+ */
+@RequiredArgsConstructor
+@Service
+public class DmiDataOperations {
+
+    private final InventoryPersistence inventoryPersistence;
+    private final JsonObjectMapper jsonObjectMapper;
+    private final DmiProperties dmiProperties;
+    private final DmiRestClient dmiRestClient;
+    private final PolicyExecutor policyExecutor;
+
+    /**
+     * This method fetches the resource data from the operational data store for a given CM handle
+     * identifier on the specified resource using the DMI client.
+     *
+     * @param cmResourceAddress Target datastore, CM handle, and resource identifier.
+     * @param options           Options query string.
+     * @param topic             Topic name for triggering asynchronous responses.
+     * @param requestId         Request ID for asynchronous responses.
+     * @param authorization     Contents of the Authorization header, or null if not present.
+     * @return {@code Mono<ResponseEntity<Object>>} A reactive type representing the response entity.
+     */
+    @Timed(value = "cps.ncmp.dmi.get",
+            description = "Time taken to fetch the resource data from operational data store for given cm handle "
+                    + "identifier on given resource using dmi client")
+    public Mono<ResponseEntity<Object>> getResourceDataFromDmi(final CmResourceAddress cmResourceAddress,
+                                                               final String options,
+                                                               final String topic,
+                                                               final String requestId,
+                                                               final String authorization) {
+        final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmResourceAddress.getResolvedCmHandleId());
+        final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
+        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
+        final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle);
+        final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters(cmResourceAddress
+                .getDatastoreName(), yangModelCmHandle, cmResourceAddress.getResourceIdentifier(), options, topic);
+        return dmiRestClient.asynchronousPostOperationWithJsonData(DATA, urlTemplateParameters, jsonRequestBody, READ,
+                authorization);
+    }
+
+    /**
+     * This method fetches all the resource data from operational data store for given cm handle
+     * identifier using dmi client.
+     * Note: this method is only used for DataSync
+     *
+     * @param cmHandleId    network resource identifier
+     * @param requestId     requestId for async responses
+     * @return {@code ResponseEntity} response entity
+     */
+    public ResponseEntity<Object> getAllResourceDataFromDmi(final String cmHandleId, final String requestId) {
+        final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
+        final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
+        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
+
+        final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle);
+        final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters(
+                PASSTHROUGH_OPERATIONAL.getDatastoreName(), yangModelCmHandle, "/", null,
+                null);
+        return dmiRestClient.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, jsonRequestBody, READ,
+                null);
+    }
+
+    /**
+     * This method requests the resource data by data store for given list of cm handles using dmi client.
+     * The data wil be returned as message on the topic specified.
+     *
+     * @param topicParamInQuery        topic name for (triggering) async responses
+     * @param dataOperationRequest     data operation request to execute operations
+     * @param requestId                requestId for as a response
+     * @param authorization            contents of Authorization header, or null if not present
+     */
+    public void requestResourceDataFromDmi(final String topicParamInQuery,
+                                           final DataOperationRequest dataOperationRequest,
+                                           final String requestId,
+                                           final String authorization)  {
+
+        final Set<String> cmHandlesIds = getDistinctCmHandleIds(dataOperationRequest);
+
+        final Collection<YangModelCmHandle> yangModelCmHandles
+            = inventoryPersistence.getYangModelCmHandles(cmHandlesIds);
+
+        final Map<String, List<DmiDataOperation>> operationsOutPerDmiServiceName
+                = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(topicParamInQuery,
+                requestId, dataOperationRequest, yangModelCmHandles);
+
+        asyncSendMultipleRequest(requestId, topicParamInQuery, operationsOutPerDmiServiceName,
+                authorization);
+    }
+
+    /**
+     * This method creates the resource data from pass-through running data store for given cm handle
+     * identifier on given resource using dmi client.
+     *
+     * @param cmHandleId    network resource identifier
+     * @param resourceId    resource identifier
+     * @param operationType operation enum
+     * @param requestData   the request data
+     * @param dataType      data type
+     * @param authorization contents of Authorization header, or null if not present
+     * @return {@code ResponseEntity} response entity
+     */
+    public ResponseEntity<Object> writeResourceDataPassThroughRunningFromDmi(final String cmHandleId,
+                                                                             final String resourceId,
+                                                                             final OperationType operationType,
+                                                                             final String requestData,
+                                                                             final String dataType,
+                                                                             final String authorization) {
+        final CmResourceAddress cmResourceAddress =
+                new CmResourceAddress(PASSTHROUGH_RUNNING.getDatastoreName(), cmHandleId, resourceId);
+
+        final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmResourceAddress.getResolvedCmHandleId());
+
+        policyExecutor.checkPermission(yangModelCmHandle, operationType, authorization, resourceId, requestData);
+
+        final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
+        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
+
+        final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType,
+                yangModelCmHandle);
+        final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters(
+                PASSTHROUGH_RUNNING.getDatastoreName(), yangModelCmHandle, resourceId, null,
+                null);
+        return dmiRestClient.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, jsonRequestBody,
+                operationType, authorization);
+    }
+
+    private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
+        return inventoryPersistence.getYangModelCmHandle(cmHandleId);
+    }
+
+    private String getDmiRequestBody(final OperationType operationType,
+                                     final String requestId,
+                                     final String requestData,
+                                     final String dataType,
+                                     final YangModelCmHandle yangModelCmHandle) {
+        final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
+                .operationType(operationType)
+                .requestId(requestId)
+                .data(requestData)
+                .dataType(dataType)
+                .moduleSetTag(yangModelCmHandle.getModuleSetTag())
+                .build();
+        dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties());
+        return jsonObjectMapper.asJsonString(dmiRequestBody);
+    }
+
+    private UrlTemplateParameters getUrlTemplateParameters(final String datastoreName,
+                                                           final YangModelCmHandle yangModelCmHandle,
+                                                           final String resourceIdentifier,
+                                                           final String optionsParamInQuery,
+                                                           final String topicParamInQuery) {
+        final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(DATA);
+        return RestServiceUrlTemplateBuilder.newInstance()
+                .fixedPathSegment("ch")
+                .variablePathSegment("cmHandleId", yangModelCmHandle.getId())
+                .fixedPathSegment("data")
+                .fixedPathSegment("ds")
+                .variablePathSegment("datastore", datastoreName)
+                .queryParameter("resourceIdentifier", resourceIdentifier)
+                .queryParameter("options", optionsParamInQuery)
+                .queryParameter("topic", topicParamInQuery)
+                .createUrlTemplateParameters(dmiServiceName, dmiProperties.getDmiBasePath());
+    }
+
+    private UrlTemplateParameters getUrlTemplateParameters(final String dmiServiceName,
+                                                           final String requestId,
+                                                           final String topicParamInQuery) {
+        return RestServiceUrlTemplateBuilder.newInstance()
+                .fixedPathSegment("data")
+                .queryParameter("requestId", requestId)
+                .queryParameter("topic", topicParamInQuery)
+                .createUrlTemplateParameters(dmiServiceName, dmiProperties.getDmiBasePath());
+    }
+
+    private void validateIfCmHandleStateReady(final YangModelCmHandle yangModelCmHandle,
+                                              final CmHandleState cmHandleState) {
+        if (cmHandleState != CmHandleState.READY) {
+            throw new CpsException("State mismatch exception.", "Cm-Handle not in READY state. "
+                    + "cm handle state is "
+                    + yangModelCmHandle.getCompositeState().getCmHandleState());
+        }
+    }
+
+    private static Set<String> getDistinctCmHandleIds(final DataOperationRequest dataOperationRequest) {
+        return dataOperationRequest.getDataOperationDefinitions().stream()
+                .flatMap(dataOperationDefinition ->
+                        dataOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet());
+    }
+
+    private void asyncSendMultipleRequest(final String requestId, final String topicParamInQuery,
+                                          final Map<String, List<DmiDataOperation>> dmiDataOperationsPerDmi,
+                                          final String authorization) {
+
+        Flux.fromIterable(dmiDataOperationsPerDmi.entrySet())
+                .flatMap(entry -> {
+                    final String dmiServiceName = entry.getKey();
+                    final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters(dmiServiceName,
+                            requestId, topicParamInQuery);
+                    final List<DmiDataOperation> dmiDataOperations = entry.getValue();
+                    final String dmiDataOperationRequestAsJsonString
+                            = createDmiDataOperationRequestAsJsonString(dmiDataOperations);
+                    return dmiRestClient.asynchronousPostOperationWithJsonData(DATA, urlTemplateParameters,
+                                    dmiDataOperationRequestAsJsonString, READ, authorization)
+                            .then()
+                            .onErrorResume(DmiClientRequestException.class, dmiClientRequestException -> {
+                                final String dataOperationResourceUrl = UriComponentsBuilder
+                                        .fromUriString(urlTemplateParameters.urlTemplate())
+                                        .buildAndExpand(urlTemplateParameters.urlVariables())
+                                        .toUriString();
+                                handleTaskCompletionException(dmiClientRequestException, dataOperationResourceUrl,
+                                        dmiDataOperations);
+                                return Mono.empty();
+                            });
+                }).subscribe();
+    }
+
+    private String createDmiDataOperationRequestAsJsonString(
+            final List<DmiDataOperation> dmiDataOperationRequestBodies) {
+        final DmiDataOperationRequest dmiDataOperationRequest = DmiDataOperationRequest.builder()
+                .operations(dmiDataOperationRequestBodies)
+                .build();
+        return jsonObjectMapper.asJsonString(dmiDataOperationRequest);
+    }
+
+    private void handleTaskCompletionException(final DmiClientRequestException dmiClientRequestException,
+                                               final String dataOperationResourceUrl,
+                                               final List<DmiDataOperation> dmiDataOperations) {
+        final MultiValueMap<String, String> dataOperationResourceUrlParameters =
+                UriComponentsBuilder.fromUriString(dataOperationResourceUrl).build().getQueryParams();
+        final String topicName = dataOperationResourceUrlParameters.get("topic").get(0);
+        final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0);
+
+        final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>>
+                cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
+
+        dmiDataOperations.forEach(dmiDataOperationRequestBody -> {
+            final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream()
+                    .map(DmiOperationCmHandle::getId).toList();
+            cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
+                    Map.of(dmiClientRequestException.getNcmpResponseStatus(), cmHandleIds));
+        });
+        DmiDataOperationsHelper.publishErrorMessageToClientTopic(topicName, requestId,
+                cmHandleIdsPerResponseCodesPerOperation);
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java
new file mode 100644 (file)
index 0000000..01022cc
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022-2024 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.impl.data;
+
+import java.util.Collection;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Service
+@RequiredArgsConstructor
+public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandler {
+
+    private final CpsDataService cpsDataService;
+    private final NetworkCmProxyQueryService networkCmProxyQueryService;
+
+    /**
+     * Executes a synchronous query request for given cm handle.
+     * Note. Currently only ncmp-datastore:operational supports query operations.
+     *
+     * @param cmHandleId         the cm handle
+     * @param resourceIdentifier the resource identifier
+     * @param includeDescendants whether include descendants
+     * @return a collection of data nodes
+     */
+    public Collection<DataNode> executeRequest(final String cmHandleId, final String resourceIdentifier,
+                                                 final boolean includeDescendants) {
+        final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants);
+        return networkCmProxyQueryService.queryResourceDataOperational(cmHandleId, resourceIdentifier,
+            fetchDescendantsOption);
+    }
+
+    @Override
+    protected Mono<Object> getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
+                                                      final String optionsParamInQuery,
+                                                      final String topicParamInQuery,
+                                                      final String requestId,
+                                                      final boolean includeDescendants,
+                                                      final String authorization) {
+        final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants);
+
+        final DataNode dataNode = cpsDataService.getDataNodes(cmResourceAddress.getDatastoreName(),
+            cmResourceAddress.getResolvedCmHandleId(),
+            cmResourceAddress.getResourceIdentifier(),
+            fetchDescendantsOption).iterator().next();
+        return Mono.justOrEmpty(dataNode);
+    }
+
+    private static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) {
+        return includeDescendants ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+            : FetchDescendantsOption.OMIT_DESCENDANTS;
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandler.java
new file mode 100644 (file)
index 0000000..f0a8c6c
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022-2024 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.impl.data;
+
+import java.util.Map;
+import java.util.UUID;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress;
+import org.onap.cps.ncmp.utils.events.TopicValidator;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@Service
+public abstract class NcmpDatastoreRequestHandler {
+
+    private static final String NO_REQUEST_ID = null;
+    private static final String NO_TOPIC = null;
+
+    @Value("${notification.async.executor.time-out-value-in-ms:60000}")
+    protected int timeOutInMilliSeconds;
+    @Value("${notification.enabled:true}")
+    protected boolean notificationFeatureEnabled;
+
+    /**
+     * Executes synchronous/asynchronous get request for given cm handle.
+     *
+     * @param cmResourceAddress   the name of the datastore, cm handle and resource identifier
+     * @param options             options to pass through to dmi client
+     * @param topic               topic (optional) for asynchronous responses
+     * @param includeDescendants  whether include descendants
+     * @param authorization       contents of Authorization header, or null if not present
+     * @return the result object, depends on use op topic. With topic a map object with request id is returned
+     *         otherwise the result of the request.
+     */
+    public Object executeRequest(final CmResourceAddress cmResourceAddress,
+                                 final String options,
+                                 final String topic,
+                                 final boolean includeDescendants,
+                                 final String authorization) {
+
+        final boolean asyncResponseRequested = topic != null;
+        if (asyncResponseRequested && notificationFeatureEnabled) {
+            return getResourceDataAsynchronously(cmResourceAddress, options, topic, includeDescendants, authorization);
+        }
+
+        if (asyncResponseRequested) {
+            log.warn("Asynchronous request is unavailable as notification feature is currently disabled, "
+                    + "will use synchronous operation.");
+        }
+        final Mono<Object> resourceDataMono = getResourceDataForCmHandle(cmResourceAddress, options,
+                NO_TOPIC, NO_REQUEST_ID, includeDescendants, authorization);
+        return resourceDataMono.block();
+    }
+
+    private Map<String, String> getResourceDataAsynchronously(final CmResourceAddress cmResourceAddress,
+                                                              final String options,
+                                                              final String topic,
+                                                              final boolean includeDescendants,
+                                                              final String authorization) {
+        TopicValidator.validateTopicName(topic);
+        final String requestId = UUID.randomUUID().toString();
+        getResourceDataForCmHandle(cmResourceAddress, options, topic, requestId, includeDescendants, authorization)
+                .doOnSuccess(result ->
+                    log.debug("Async operation succeeded for request id {}: {}", requestId, result))
+                .subscribe();
+        log.debug("Received Async request with id {}", requestId);
+        return Map.of("requestId", requestId);
+    }
+
+    protected abstract Mono<Object> getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
+                                                               final String optionsParamInQuery,
+                                                               final String topicParamInQuery,
+                                                               final String requestId,
+                                                               final boolean includeDescendant,
+                                                               final String authorization);
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpPassthroughResourceRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpPassthroughResourceRequestHandler.java
new file mode 100644 (file)
index 0000000..a21210c
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022-2024 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.impl.data;
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL;
+import static org.onap.cps.ncmp.api.data.models.OperationType.READ;
+
+import java.util.Map;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException;
+import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException;
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress;
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException;
+import org.onap.cps.ncmp.utils.events.TopicValidator;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Service
+@RequiredArgsConstructor
+public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestHandler {
+
+    private final DmiDataOperations dmiDataOperations;
+
+    private static final int MAXIMUM_CM_HANDLES_PER_OPERATION = 200;
+    private static final String PAYLOAD_TOO_LARGE_TEMPLATE = "Operation '%s' affects too many (%d) cm handles";
+
+    /**
+     * Executes asynchronous request for group of cm handles to resource data.
+     *
+     * @param topic                 the topic param in query
+     * @param dataOperationRequest  data operation request details for resource data
+     * @param authorization         contents of Authorization header, or null if not present
+     * @return a map with one entry of request Id for success or status and error when async feature is disabled
+     */
+    public Map<String, String> executeAsynchronousRequest(final String topic,
+                                                          final DataOperationRequest dataOperationRequest,
+                                                          final String authorization) {
+        validateDataOperationRequest(topic, dataOperationRequest);
+        if (!notificationFeatureEnabled) {
+            return Map.of("status",
+                "Asynchronous request is unavailable as notification feature is currently disabled.");
+        }
+        final String requestId = UUID.randomUUID().toString();
+        dmiDataOperations.requestResourceDataFromDmi(topic, dataOperationRequest, requestId, authorization);
+        return Map.of("requestId", requestId);
+    }
+
+    @Override
+    protected Mono<Object> getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
+                                                      final String options,
+                                                      final String topic,
+                                                      final String requestId,
+                                                      final boolean includeDescendants,
+                                                      final String authorization) {
+
+        return dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, options, topic, requestId, authorization)
+            .flatMap(responseEntity -> Mono.justOrEmpty(responseEntity.getBody()));
+    }
+
+    private void validateDataOperationRequest(final String topicParamInQuery,
+                                              final DataOperationRequest dataOperationRequest) {
+        TopicValidator.validateTopicName(topicParamInQuery);
+        dataOperationRequest.getDataOperationDefinitions().forEach(dataOperationDefinition -> {
+            if (OperationType.fromOperationName(dataOperationDefinition.getOperation()) != READ) {
+                throw new OperationNotSupportedException(
+                        dataOperationDefinition.getOperation() + " operation not yet supported");
+            }
+            if (DatastoreType.fromDatastoreName(dataOperationDefinition.getDatastore()) == OPERATIONAL) {
+                throw new InvalidDatastoreException(dataOperationDefinition.getDatastore()
+                        + " datastore is not supported");
+            }
+            if (dataOperationDefinition.getCmHandleIds().size() > MAXIMUM_CM_HANDLES_PER_OPERATION) {
+                final String errorMessage = String.format(PAYLOAD_TOO_LARGE_TEMPLATE,
+                        dataOperationDefinition.getOperationId(),
+                        dataOperationDefinition.getCmHandleIds().size());
+                throw new PayloadTooLargeException(errorMessage);
+            }
+        });
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java
new file mode 100644 (file)
index 0000000..5343a2e
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 highstreet technologies GmbH
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2021 Pantheon.tech
+ *  Modifications Copyright (C) 2021-2022 Bell Canada
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  ================================================================================
+ *  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.impl.data;
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL;
+
+import java.util.Collection;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress;
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class NetworkCmProxyFacade {
+
+    private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler;
+    private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler;
+    private final DmiDataOperations dmiDataOperations;
+    private final AlternateIdMatcher alternateIdMatcher;
+
+    /**
+     * Fetches resource data for a given data store using DMI (Data Management Interface).
+     * This method retrieves data based on the provided CmResourceAddress and additional query parameters.
+     * It supports asynchronous processing and handles authorization if required.
+     *
+     * @param cmResourceAddress     The target data store, including the CM handle and resource identifier.
+     *                              This parameter must not be null.
+     * @param optionsParamInQuery   Additional query parameters that may influence the data retrieval process,
+     *                              such as filters or limits. This parameter can be null.
+     * @param topicParamInQuery     The topic name for triggering asynchronous responses. If specified,
+     *                              the response will be sent to this topic. This parameter can be null.
+     * @param includeDescendants    include (all) descendants or not
+     * @param authorization         The contents of the Authorization header. This parameter can be null
+     *                              if authorization is not required.
+     * @return the result object, depends on use op topic. With topic a map object with request id is returned
+     *         otherwise the result of the request.
+     */
+    public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
+                                             final String optionsParamInQuery,
+                                             final String topicParamInQuery,
+                                             final Boolean includeDescendants,
+                                             final String authorization) {
+
+        final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler
+            = getNcmpDatastoreRequestHandler(cmResourceAddress.getDatastoreName());
+        return ncmpDatastoreRequestHandler.executeRequest(cmResourceAddress, optionsParamInQuery,
+            topicParamInQuery, includeDescendants, authorization);
+    }
+
+    /**
+     * Executes asynchronous request for group of cm handles to resource data.
+     *
+     * @param topic                    the topic param in query
+     * @param dataOperationRequest     data operation request details for resource data
+     * @param authorization            contents of Authorization header, or null if not present
+     * @return a map with one entry of request Id for success or status and error when async feature is disabled
+     */
+    public Object executeDataOperationForCmHandles(final String topic,
+                                                   final DataOperationRequest dataOperationRequest,
+                                                   final String authorization) {
+        return ncmpPassthroughResourceRequestHandler.executeAsynchronousRequest(topic,
+                                                                                dataOperationRequest,
+                                                                                authorization);
+    }
+
+    public Collection<DataNode> queryResourceDataForCmHandle(final String cmHandle,
+                                                             final String cpsPath,
+                                                             final Boolean includeDescendants) {
+        return ncmpCachedResourceRequestHandler.executeRequest(cmHandle, cpsPath, includeDescendants);
+    }
+
+    /**
+     * Write resource data for data store pass-through running using dmi for given cm-handle.
+     *
+     * @param cmHandleReference         cm handle or alternate identifier
+     * @param resourceIdentifier resource identifier
+     * @param operationType      required operation type
+     * @param requestData        request body to create resource
+     * @param dataType        content type in body
+     * @param authorization       contents of Authorization header, or null if not present
+     * @return {@code Object} return data
+     */
+    public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleReference,
+                                                                 final String resourceIdentifier,
+                                                                 final OperationType operationType,
+                                                                 final String requestData,
+                                                                 final String dataType,
+                                                                 final String authorization) {
+        return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleReference, resourceIdentifier,
+            operationType, requestData, dataType, authorization);
+    }
+
+    private NcmpDatastoreRequestHandler getNcmpDatastoreRequestHandler(final String datastoreName) {
+        if (OPERATIONAL.equals(DatastoreType.fromDatastoreName(datastoreName))) {
+            return ncmpCachedResourceRequestHandler;
+        }
+        return ncmpPassthroughResourceRequestHandler;
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryService.java
new file mode 100644 (file)
index 0000000..39abdc5
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022-2024 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.impl.data;
+
+import java.util.Collection;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.DataNode;
+
+/*
+ * Datastore interface for handling cached CPS data query requests.
+ */
+public interface NetworkCmProxyQueryService {
+
+    /**
+     * Fetches operational resource data based on the provided CM handle identifier and CPS path.
+     * This method retrieves data nodes from the specified path within the context of a given CM handle.
+     * It supports options for fetching descendant nodes.
+     *
+     * @param cmHandleId             The CM handle identifier, which uniquely identifies the CM handle.
+     *                               This parameter must not be null.
+     * @param cpsPath                The CPS (Control Plane Service) path specifying the location of the
+     *                               resource data within the CM handle. This parameter must not be null.
+     * @param fetchDescendantsOption The option specifying whether to fetch descendant nodes along with the specified
+     *                               resource data.
+     * @return {@code Collection<DataNode>} A collection of DataNode objects representing the resource data
+     *     retrieved from the specified path. The collection may include descendant nodes based on the
+     *     fetchDescendantsOption.
+     */
+    Collection<DataNode> queryResourceDataOperational(String cmHandleId,
+                                                      String cpsPath,
+                                                      FetchDescendantsOption fetchDescendantsOption);
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl;
+package org.onap.cps.ncmp.impl.data;
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
 
+import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsQueryService;
-import org.onap.cps.ncmp.api.NetworkCmProxyQueryService;
 import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.DataNode;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -37,9 +38,9 @@ public class NetworkCmProxyQueryServiceImpl implements NetworkCmProxyQueryServic
     private final CpsQueryService cpsQueryService;
 
     @Override
-    public Object queryResourceDataOperational(final String cmHandleId,
-                                               final String cpsPath,
-                                               final FetchDescendantsOption fetchDescendantsOption) {
+    public Collection<DataNode> queryResourceDataOperational(final String cmHandleId,
+                                                             final String cpsPath,
+                                                             final FetchDescendantsOption fetchDescendantsOption) {
         return cpsQueryService.queryDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, cpsPath,
             fetchDescendantsOption);
     }
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.impl.data.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@@ -26,7 +26,9 @@ import java.util.ArrayList;
 import java.util.List;
 import lombok.Builder;
 import lombok.Getter;
-import org.onap.cps.ncmp.api.models.DataOperationDefinition;
+import org.onap.cps.ncmp.api.data.models.DataOperationDefinition;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.api.data.models.OperationType;
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @Getter
@@ -40,7 +42,7 @@ public class DmiDataOperation {
     private String options;
     private String resourceIdentifier;
 
-    private final List<CmHandle> cmHandles = new ArrayList<>();
+    private final List<DmiOperationCmHandle> cmHandles = new ArrayList<>();
 
     /**
      * Create and initialise a (outgoing) DMI data operation.
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.impl.data.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -29,14 +29,21 @@ import lombok.Getter;
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @Getter
 @Builder
-public class CmHandle {
+public class DmiOperationCmHandle {
     private String id;
 
     @JsonProperty("cmHandleProperties")
     private Map<String, String> dmiProperties;
+    private String moduleSetTag;
 
-    public static CmHandle buildCmHandleWithProperties(final String cmHandleId,
-                                                       final Map<String, String> dmiProperties) {
-        return CmHandle.builder().id(cmHandleId).dmiProperties(dmiProperties).build();
+    /**
+     * Builds Dmi Operation Cm Handle object with all its associated properties.
+     */
+    public static DmiOperationCmHandle buildDmiOperationCmHandle(final String cmHandleId,
+                                                                 final Map<String, String> dmiProperties,
+                                                                 final String moduleSetTag) {
+        return DmiOperationCmHandle.builder().id(cmHandleId)
+                .dmiProperties(dmiProperties).moduleSetTag(moduleSetTag)
+                .build();
     }
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java
new file mode 100644 (file)
index 0000000..96771e3
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.data.policyexecutor;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.exceptions.NcmpException;
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PolicyExecutor {
+
+    @Value("${ncmp.policy-executor.enabled:false}")
+    private boolean enabled;
+
+    @Value("${ncmp.policy-executor.server.address:http://policy-executor}")
+    private String serverAddress;
+
+    @Value("${ncmp.policy-executor.server.port:8080}")
+    private String serverPort;
+
+    @Qualifier("policyExecutorWebClient")
+    private final WebClient policyExecutorWebClient;
+
+    private final ObjectMapper objectMapper;
+
+    /**
+     * Use the Policy Executor to check permission for a cm write operation.
+     * Wil throw an exception when the operation is not permitted (work in progress)
+     *
+     * @param yangModelCmHandle   the cm handle involved
+     * @param operationType       the write operation
+     * @param authorization       the original rest authorization token (can be used to determine the client)
+     * @param resourceIdentifier  the resource identifier (can be blank)
+     * @param changeRequestAsJson the change details from the original rest request in json format
+     */
+    public void checkPermission(final YangModelCmHandle yangModelCmHandle,
+                                final OperationType operationType,
+                                final String authorization,
+                                final String resourceIdentifier,
+                                final String changeRequestAsJson) {
+        log.trace("Policy Executor Enabled: {}", enabled);
+        if (enabled) {
+            final ResponseEntity<JsonNode> responseEntity =
+                getPolicyExecutorResponse(yangModelCmHandle, operationType, authorization, resourceIdentifier,
+                    changeRequestAsJson);
+
+            if (responseEntity == null) {
+                log.warn("No valid response from policy, ignored");
+                return;
+            }
+
+            if (responseEntity.getStatusCode().is2xxSuccessful()) {
+                if (responseEntity.getBody() == null) {
+                    log.warn("No valid response body from policy, ignored");
+                    return;
+                }
+                processResponse(responseEntity.getBody());
+            } else {
+                log.warn("Policy Executor invocation failed with status {}",
+                    responseEntity.getStatusCode().value());
+                throw new ServerNcmpException("Policy Executor invocation failed", "HTTP status code: "
+                    + responseEntity.getStatusCode().value());
+            }
+        }
+    }
+
+    private Map<String, Object> getSingleRequestAsMap(final YangModelCmHandle yangModelCmHandle,
+                                                      final OperationType operationType,
+                                                      final String resourceIdentifier,
+                                                      final String changeRequestAsJson) {
+        final Map<String, Object> data = new HashMap<>(4);
+        data.put("cmHandleId", yangModelCmHandle.getId());
+        data.put("resourceIdentifier", resourceIdentifier);
+        data.put("targetIdentifier", yangModelCmHandle.getAlternateId());
+        if (!OperationType.DELETE.equals(operationType)) {
+            try {
+                final Object changeRequestAsObject = objectMapper.readValue(changeRequestAsJson, Object.class);
+                data.put("cmChangeRequest", changeRequestAsObject);
+            } catch (final JsonProcessingException e) {
+                throw new NcmpException("Cannot convert Change Request data to Object",
+                    "Invalid Json: " + changeRequestAsJson);
+            }
+        }
+        final Map<String, Object> request = new HashMap<>(2);
+        request.put("schema", getAssociatedPolicyDataSchemaName(operationType));
+        request.put("data", data);
+        return request;
+    }
+
+    private static String getAssociatedPolicyDataSchemaName(final OperationType operationType) {
+        return "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-" + operationType.getOperationName() + "-schema:1.0.0";
+    }
+
+    private Object createBodyAsObject(final List<Object> requests) {
+        final Map<String, Object> bodyAsMap = new HashMap<>(2);
+        bodyAsMap.put("decisionType", "allow");
+        bodyAsMap.put("requests", requests);
+        return bodyAsMap;
+    }
+
+    private ResponseEntity<JsonNode> getPolicyExecutorResponse(final YangModelCmHandle yangModelCmHandle,
+                                                               final OperationType operationType,
+                                                               final String authorization,
+                                                               final String resourceIdentifier,
+                                                               final String changeRequestAsJson) {
+
+
+        final Map<String, Object> requestAsMap = getSingleRequestAsMap(yangModelCmHandle,
+            operationType,
+            resourceIdentifier,
+            changeRequestAsJson);
+
+        final Object bodyAsObject = createBodyAsObject(Collections.singletonList(requestAsMap));
+
+        final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
+                .fixedPathSegment("execute")
+                .createUrlTemplateParameters(String.format("%s:%s", serverAddress, serverPort),
+                        "policy-executor/api");
+
+        return policyExecutorWebClient.post()
+            .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
+            .header(HttpHeaders.AUTHORIZATION, authorization)
+            .body(BodyInserters.fromValue(bodyAsObject))
+            .retrieve()
+            .toEntity(JsonNode.class)
+            .block();
+    }
+
+    private static void processResponse(final JsonNode responseBody) {
+        final String decisionId = responseBody.path("decisionId").asText("unknown id");
+        log.trace("Policy Executor Decision ID: {} ", decisionId);
+        final String decision = responseBody.path("decision").asText("unknown");
+        if ("allow".equals(decision)) {
+            log.trace("Policy Executor allows the operation");
+        } else {
+            log.warn("Policy Executor decision: {}", decision);
+            final String details = responseBody.path("message").asText();
+            log.warn("Policy Executor message: {}", details);
+            final String message = "Policy Executor did not allow request. Decision #"
+                + decisionId + " : " + decision;
+            throw new PolicyExecutorException(message, details);
+        }
+    }
+
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils.data.operation;
+package org.onap.cps.ncmp.impl.data.utils;
 
 import io.cloudevents.CloudEvent;
 import java.util.ArrayList;
@@ -29,11 +29,11 @@ import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.NcmpResponseStatus;
-import org.onap.cps.ncmp.api.impl.events.NcmpEvent;
-import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation;
 import org.onap.cps.ncmp.events.async1_0_0.Data;
 import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent;
 import org.onap.cps.ncmp.events.async1_0_0.Response;
+import org.onap.cps.ncmp.impl.data.models.DmiDataOperation;
+import org.onap.cps.ncmp.utils.events.NcmpEvent;
 import org.springframework.util.MultiValueMap;
 
 @Slf4j
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils.data.operation;
+package org.onap.cps.ncmp.impl.data.utils;
 
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND;
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_READY;
@@ -31,27 +31,26 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.events.EventsPublisher;
 import org.onap.cps.ncmp.api.NcmpResponseStatus;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.operations.CmHandle;
-import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation;
-import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer;
-import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.DataOperationDefinition;
-import org.onap.cps.ncmp.api.models.DataOperationRequest;
+import org.onap.cps.ncmp.api.data.models.DataOperationDefinition;
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest;
+import org.onap.cps.ncmp.config.CpsApplicationContext;
+import org.onap.cps.ncmp.impl.data.models.DmiDataOperation;
+import org.onap.cps.ncmp.impl.data.models.DmiOperationCmHandle;
+import org.onap.cps.ncmp.impl.dmi.DmiServiceNameOrganizer;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 
-@Slf4j
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class ResourceDataOperationRequestUtils {
+@Slf4j
+public class DmiDataOperationsHelper {
 
     private static final String UNKNOWN_SERVICE_NAME = null;
 
@@ -81,6 +80,8 @@ public class ResourceDataOperationRequestUtils {
         final Map<String, String> dmiServiceNamesPerCmHandleId =
                 getDmiServiceNamesPerCmHandleId(dmiPropertiesPerCmHandleIdPerServiceName);
 
+        final Map<String, String> moduleSetTagPerCmHandle = getModuleSetTagPerCmHandleId(yangModelCmHandles);
+
         for (final DataOperationDefinition dataOperationDefinitionIn :
                 dataOperationRequestIn.getDataOperationDefinitions()) {
             final List<String> nonExistingCmHandleIds = new ArrayList<>();
@@ -97,9 +98,10 @@ public class ResourceDataOperationRequestUtils {
                     } else {
                         final DmiDataOperation dmiBatchOperationOut = getOrAddDmiBatchOperation(dmiServiceName,
                                 dataOperationDefinitionIn, dmiDataOperationsOutPerDmiServiceName);
-                        final CmHandle cmHandle = CmHandle.buildCmHandleWithProperties(cmHandleId,
-                                cmHandleIdProperties);
-                        dmiBatchOperationOut.getCmHandles().add(cmHandle);
+                        final DmiOperationCmHandle dmiOperationCmHandle = DmiOperationCmHandle
+                                .buildDmiOperationCmHandle(cmHandleId, cmHandleIdProperties,
+                                        moduleSetTagPerCmHandle.get(cmHandleId));
+                        dmiBatchOperationOut.getCmHandles().add(dmiOperationCmHandle);
                     }
                 }
             }
@@ -114,56 +116,12 @@ public class ResourceDataOperationRequestUtils {
         return dmiDataOperationsOutPerDmiServiceName;
     }
 
-    /**
-     * Handles the async task completion for an entire data, publishing errors to client topic on task failure.
-     *
-     * @param topicParamInQuery      client given topic
-     * @param requestId              unique identifier per request
-     * @param dataOperationRequest   incoming data operation request details
-     * @param throwable              error cause, or null if task completed with no exception
-     */
-    public static void handleAsyncTaskCompletionForDataOperationsRequest(
-            final String topicParamInQuery,
-            final String requestId,
-            final DataOperationRequest dataOperationRequest,
-            final Throwable throwable) {
-        if (throwable == null) {
-            log.info("Data operations request {} completed.", requestId);
-        } else if (throwable instanceof TimeoutException) {
-            log.error("Data operations request {} timed out.", requestId);
-            ResourceDataOperationRequestUtils.publishErrorMessageToClientTopicForEntireOperation(topicParamInQuery,
-                    requestId, dataOperationRequest, NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING);
-        } else {
-            log.error("Data operations request {} failed.", requestId, throwable);
-            ResourceDataOperationRequestUtils.publishErrorMessageToClientTopicForEntireOperation(topicParamInQuery,
-                    requestId, dataOperationRequest, NcmpResponseStatus.UNKNOWN_ERROR);
-        }
-    }
-
-    /**
-     * Creates data operation cloud event for when the entire data operation fails and publishes it to client topic.
-     *
-     * @param topicParamInQuery      client given topic
-     * @param requestId              unique identifier per request
-     * @param dataOperationRequestIn incoming data operation request details
-     * @param ncmpResponseStatus     response code to be sent for all cm handle ids in all operations
-     */
-    private static void publishErrorMessageToClientTopicForEntireOperation(
-            final String topicParamInQuery,
-            final String requestId,
-            final DataOperationRequest dataOperationRequestIn,
-            final NcmpResponseStatus ncmpResponseStatus) {
-
-        final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>>
-                cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
-
-        for (final DataOperationDefinition dataOperationDefinitionIn :
-                dataOperationRequestIn.getDataOperationDefinitions()) {
-            cmHandleIdsPerResponseCodesPerOperation.add(
-                    DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn),
-                    Map.of(ncmpResponseStatus, dataOperationDefinitionIn.getCmHandleIds()));
-        }
-        publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperation);
+    private static Map<String, String> getModuleSetTagPerCmHandleId(
+                                                       final Collection<YangModelCmHandle> yangModelCmHandles) {
+        final Map<String, String> moduleSetTagPerCmHandle = new HashMap<>(yangModelCmHandles.size());
+        yangModelCmHandles.forEach(yangModelCmHandle ->
+                moduleSetTagPerCmHandle.put(yangModelCmHandle.getId(), yangModelCmHandle.getModuleSetTag()));
+        return moduleSetTagPerCmHandle;
     }
 
     /**
@@ -182,6 +140,8 @@ public class ResourceDataOperationRequestUtils {
             final CloudEvent dataOperationCloudEvent = DataOperationEventCreator.createDataOperationEvent(clientTopic,
                     requestId, cmHandleIdsPerResponseCodesPerOperation);
             final EventsPublisher<CloudEvent> eventsPublisher = CpsApplicationContext.getCpsBean(EventsPublisher.class);
+            log.warn("publishing error message to client topic: {} ,requestId: {}, data operation cloud event id: {}",
+                    clientTopic, requestId, dataOperationCloudEvent.getId());
             eventsPublisher.publishCloudEvent(clientTopic, requestId, dataOperationCloudEvent);
         }
     }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java
new file mode 100644 (file)
index 0000000..8934c08
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs;
+
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.datajobs.DataJobResultService;
+import org.onap.cps.ncmp.impl.dmi.DmiProperties;
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DataJobResultServiceImpl implements DataJobResultService {
+
+    private final DmiRestClient dmiRestClient;
+    private final DmiProperties dmiProperties;
+
+    @Override
+    public String getDataJobResult(final String authorization,
+                                   final String dmiServiceName,
+                                   final String dataProducerId,
+                                   final String dataProducerJobId,
+                                   final String destination) {
+        final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
+                                           .fixedPathSegment("cmwriteJob")
+                                           .fixedPathSegment("dataProducer")
+                                           .variablePathSegment("dataProducerId", dataProducerId)
+                                           .fixedPathSegment("dataProducerJob")
+                                           .variablePathSegment("dataProducerJobId", dataProducerJobId)
+                                           .fixedPathSegment("result")
+                                           .queryParameter("destination", destination)
+                                           .createUrlTemplateParameters(dmiServiceName, dmiProperties.getDmiBasePath());
+        return dmiRestClient.getDataJobResult(urlTemplateParameters, authorization).block();
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImpl.java
new file mode 100644 (file)
index 0000000..04c3ad2
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs;
+
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.datajobs.DataJobService;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobReadRequest;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest;
+import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation;
+import org.onap.cps.ncmp.api.datajobs.models.ProducerKey;
+import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DataJobServiceImpl implements DataJobService {
+
+    private final DmiSubJobRequestHandler dmiSubJobClient;
+    private final WriteRequestExaminer writeRequestExaminer;
+
+    @Override
+    public void readDataJob(final String authorization,
+                            final String dataJobId,
+                            final DataJobMetadata dataJobMetadata,
+                            final DataJobReadRequest dataJobReadRequest) {
+        log.info("data job id for read operation is: {}", dataJobId);
+    }
+
+    @Override
+    public List<SubJobWriteResponse> writeDataJob(final String authorization,
+                                                  final String dataJobId,
+                                                  final DataJobMetadata dataJobMetadata,
+                                                  final DataJobWriteRequest dataJobWriteRequest) {
+        log.info("data job id for write operation is: {}", dataJobId);
+
+        final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey =
+                writeRequestExaminer.splitDmiWriteOperationsFromRequest(dataJobId, dataJobWriteRequest);
+
+        return dmiSubJobClient.sendRequestsToDmi(authorization,
+                                                 dataJobId,
+                                                 dataJobMetadata,
+                                                 dmiWriteOperationsPerProducerKey);
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java
new file mode 100644 (file)
index 0000000..1cfb8a9
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs;
+
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.datajobs.DataJobStatusService;
+import org.onap.cps.ncmp.impl.dmi.DmiProperties;
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.springframework.stereotype.Service;
+
+/**
+ * Implementation of {@link DataJobStatusService} interface.
+ * The operations interact with a DMI Plugin to retrieve data job statuses.
+ */
+@Service
+@RequiredArgsConstructor
+public class DataJobStatusServiceImpl implements DataJobStatusService {
+
+    private final DmiRestClient dmiRestClient;
+    private final DmiProperties dmiProperties;
+
+    @Override
+    public String getDataJobStatus(final String authorization,
+                                   final String dmiServiceName,
+                                   final String dataProducerId,
+                                   final String dataProducerJobId) {
+
+        final UrlTemplateParameters urlTemplateParameters = buildUrlParameters(dmiServiceName,
+                                                                              dataProducerId,
+                                                                              dataProducerJobId);
+        return dmiRestClient.getDataJobStatus(urlTemplateParameters, authorization).block();
+    }
+
+    private UrlTemplateParameters buildUrlParameters(final String dmiServiceName,
+                                                     final String dataProducerId,
+                                                     final String dataProducerJobId) {
+        return RestServiceUrlTemplateBuilder.newInstance()
+                .fixedPathSegment("cmwriteJob")
+                .fixedPathSegment("dataProducer")
+                .variablePathSegment("dataProducerId", dataProducerId)
+                .fixedPathSegment("dataProducerJob")
+                .variablePathSegment("dataProducerJobId", dataProducerJobId)
+                .fixedPathSegment("status")
+                .createUrlTemplateParameters(dmiServiceName, dmiProperties.getDmiBasePath());
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java
new file mode 100644 (file)
index 0000000..a118d53
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata;
+import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation;
+import org.onap.cps.ncmp.api.datajobs.models.ProducerKey;
+import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteRequest;
+import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse;
+import org.onap.cps.ncmp.impl.dmi.DmiProperties;
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient;
+import org.onap.cps.ncmp.impl.models.RequiredDmiService;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DmiSubJobRequestHandler {
+
+    private final DmiRestClient dmiRestClient;
+    private final DmiProperties dmiProperties;
+    private final JsonObjectMapper jsonObjectMapper;
+
+    /**
+     * Sends sub-job write requests to the DMI Plugin.
+     *
+     * @param authorization                    the authorization header from the REST request
+     * @param dataJobId                        data job identifier
+     * @param dataJobMetadata                  the data job's metadata
+     * @param dmiWriteOperationsPerProducerKey a collection of write requests per producer key
+     * @return a list of sub-job write responses
+     */
+    public List<SubJobWriteResponse> sendRequestsToDmi(final String authorization,
+                                                       final String dataJobId,
+                                                       final DataJobMetadata dataJobMetadata,
+                                     final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey) {
+        final List<SubJobWriteResponse> subJobWriteResponses = new ArrayList<>(dmiWriteOperationsPerProducerKey.size());
+        dmiWriteOperationsPerProducerKey.forEach((producerKey, dmi3ggpWriteOperations) -> {
+            final SubJobWriteRequest subJobWriteRequest = new SubJobWriteRequest(dataJobMetadata.destination(),
+                                                                                 dataJobMetadata.dataAcceptType(),
+                                                                                 dataJobMetadata.dataContentType(),
+                                                                                 producerKey.dataProducerIdentifier(),
+                                                                                 dataJobId,
+                                                                                 dmi3ggpWriteOperations);
+
+            final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters(dataJobMetadata.destination(),
+                                                                                         producerKey);
+            final ResponseEntity<Object> responseEntity = dmiRestClient.synchronousPostOperationWithJsonData(
+                    RequiredDmiService.DATA,
+                    urlTemplateParameters,
+                    jsonObjectMapper.asJsonString(subJobWriteRequest),
+                    OperationType.CREATE,
+                    authorization);
+            final SubJobWriteResponse subJobWriteResponse = jsonObjectMapper
+                                            .convertToValueType(responseEntity.getBody(), SubJobWriteResponse.class);
+            log.debug("Sub job write response: {}", subJobWriteResponse);
+            subJobWriteResponses.add(subJobWriteResponse);
+        });
+        return subJobWriteResponses;
+    }
+
+    private UrlTemplateParameters getUrlTemplateParameters(final String destination, final ProducerKey producerKey) {
+        return RestServiceUrlTemplateBuilder.newInstance()
+                .fixedPathSegment("cmwriteJob")
+                .queryParameter("destination", destination)
+                .createUrlTemplateParameters(producerKey.dmiServiceName(), dmiProperties.getDmiBasePath());
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java
new file mode 100644 (file)
index 0000000..70d08dc
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest;
+import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation;
+import org.onap.cps.ncmp.api.datajobs.models.ProducerKey;
+import org.onap.cps.ncmp.api.datajobs.models.WriteOperation;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class WriteRequestExaminer {
+
+    private final AlternateIdMatcher alternateIdMatcher;
+    private static final String PATH_SEPARATOR = "/";
+
+    /**
+     * Splitting incoming data job write request into Dmi Write Operations by ProducerKey.
+     *
+     * @param dataJobId data job identifier
+     * @param dataJobWriteRequest incoming data job write request
+     * @return {@code Map} map of Dmi Write Operations per Producer Key
+     */
+    public Map<ProducerKey, List<DmiWriteOperation>> splitDmiWriteOperationsFromRequest(
+            final String dataJobId,
+            final DataJobWriteRequest dataJobWriteRequest) {
+        final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey = new HashMap<>();
+        for (final WriteOperation writeOperation : dataJobWriteRequest.data()) {
+            examineWriteOperation(dataJobId, dmiWriteOperationsPerProducerKey, writeOperation);
+        }
+        return dmiWriteOperationsPerProducerKey;
+    }
+
+    private void examineWriteOperation(final String dataJobId,
+                                       final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey,
+                                       final WriteOperation writeOperation) {
+        log.debug("data job id for write operation is: {}", dataJobId);
+        final DataNode dataNode = alternateIdMatcher
+                .getCmHandleDataNodeByLongestMatchingAlternateId(writeOperation.path(), PATH_SEPARATOR);
+
+        final DmiWriteOperation dmiWriteOperation = createDmiWriteOperation(writeOperation, dataNode);
+
+        final ProducerKey producerKey = createProducerKey(dataNode);
+        final List<DmiWriteOperation> dmiWriteOperations;
+        if (dmiWriteOperationsPerProducerKey.containsKey(producerKey)) {
+            dmiWriteOperations = dmiWriteOperationsPerProducerKey.get(producerKey);
+        } else {
+            dmiWriteOperations = new ArrayList<>();
+            dmiWriteOperationsPerProducerKey.put(producerKey, dmiWriteOperations);
+        }
+        dmiWriteOperations.add(dmiWriteOperation);
+    }
+
+    private ProducerKey createProducerKey(final DataNode dataNode) {
+        return new ProducerKey((String) dataNode.getLeaves().get("dmi-service-name"),
+                (String) dataNode.getLeaves().get("data-producer-identifier"));
+    }
+
+    private DmiWriteOperation createDmiWriteOperation(final WriteOperation writeOperation,
+                                                      final DataNode dataNode) {
+        return new DmiWriteOperation(
+                writeOperation.path(),
+                writeOperation.op(),
+                (String) dataNode.getLeaves().get("module-set-tag"),
+                writeOperation.value(),
+                writeOperation.operationId(),
+                getPrivatePropertiesFromDataNode(dataNode));
+    }
+
+    private Map<String, String> getPrivatePropertiesFromDataNode(final DataNode dataNode) {
+        final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(dataNode);
+        final Map<String, String> cmHandleDmiProperties = new LinkedHashMap<>();
+        yangModelCmHandle.getDmiProperties()
+                .forEach(dmiProperty -> cmHandleDmiProperties.put(dmiProperty.getName(), dmiProperty.getValue()));
+        return cmHandleDmiProperties;
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiProperties.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiProperties.java
new file mode 100644 (file)
index 0000000..2f60460
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.dmi;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Getter
+@Component
+public class DmiProperties {
+    @Value("${ncmp.dmi.auth.username}")
+    private String authUsername;
+    @Value("${ncmp.dmi.auth.password}")
+    private String authPassword;
+    @Getter(AccessLevel.NONE)
+    @Value("${ncmp.dmi.api.base-path}")
+    private String dmiBasePath;
+    @Value("${ncmp.dmi.auth.enabled}")
+    private boolean dmiBasicAuthEnabled;
+
+    /**
+     * Removes both leading and trailing slashes if they are present.
+     *
+     * @return dmi base path without any slashes ("/")
+     */
+    public String getDmiBasePath() {
+        if (dmiBasePath.startsWith("/")) {
+            dmiBasePath = dmiBasePath.substring(1);
+        }
+        if (dmiBasePath.endsWith("/")) {
+            dmiBasePath = dmiBasePath.substring(0, dmiBasePath.length() - 1);
+        }
+        return dmiBasePath;
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java
new file mode 100644 (file)
index 0000000..a177272
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications 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.impl.dmi;
+
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING;
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA;
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR;
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
+import static org.springframework.http.HttpStatus.REQUEST_TIMEOUT;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Locale;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException;
+import org.onap.cps.ncmp.impl.models.RequiredDmiService;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import reactor.core.publisher.Mono;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class DmiRestClient {
+
+    private static final String NOT_SPECIFIED = "";
+    private static final String NO_AUTHORIZATION = null;
+
+    private final DmiProperties dmiProperties;
+    private final JsonObjectMapper jsonObjectMapper;
+    @Qualifier("dataServicesWebClient")
+    private final WebClient dataServicesWebClient;
+    @Qualifier("modelServicesWebClient")
+    private final WebClient modelServicesWebClient;
+    @Qualifier("healthChecksWebClient")
+    private final WebClient healthChecksWebClient;
+
+    /**
+     * Sends a synchronous (blocking) POST operation to the DMI with a JSON body containing module references.
+     *
+     * @param requiredDmiService      Determines if the required service is for a data or model operation.
+     * @param urlTemplateParameters   The DMI resource URL template with variables.
+     * @param requestBodyAsJsonString JSON data body.
+     * @param operationType           The type of operation being executed (for error reporting only).
+     * @param authorization           Contents of the Authorization header, or null if not present.
+     * @return ResponseEntity containing the response from the DMI.
+     * @throws DmiClientRequestException If there is an error during the DMI request.
+     */
+    public ResponseEntity<Object> synchronousPostOperationWithJsonData(final RequiredDmiService requiredDmiService,
+                                                                       final UrlTemplateParameters
+                                                                               urlTemplateParameters,
+                                                                       final String requestBodyAsJsonString,
+                                                                       final OperationType operationType,
+                                                                       final String authorization) {
+        final Mono<ResponseEntity<Object>> responseEntityMono =
+            asynchronousPostOperationWithJsonData(requiredDmiService,
+                    urlTemplateParameters,
+                requestBodyAsJsonString,
+                operationType,
+                authorization);
+        return responseEntityMono.block();
+    }
+
+    /**
+     * Asynchronously performs an HTTP POST operation with the given JSON data.
+     *
+     * @param requiredDmiService      The service object required for retrieving or configuring the WebClient.
+     * @param urlTemplateParameters   The URL template with variables for the POST request.
+     * @param requestBodyAsJsonString The JSON string that will be sent as the request body.
+     * @param operationType           An enumeration or object that holds information about the type of operation
+     *                                being performed.
+     * @param authorization           The authorization token to be added to the request headers.
+     * @return A Mono emitting the response entity containing the server's response.
+     */
+    public Mono<ResponseEntity<Object>> asynchronousPostOperationWithJsonData(final RequiredDmiService
+                                                                                      requiredDmiService,
+                                                                              final UrlTemplateParameters
+                                                                                      urlTemplateParameters,
+                                                                              final String requestBodyAsJsonString,
+                                                                              final OperationType operationType,
+                                                                              final String authorization) {
+        final WebClient webClient = getWebClient(requiredDmiService);
+        return webClient.post()
+                .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
+                .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization))
+                .body(BodyInserters.fromValue(requestBodyAsJsonString))
+                .retrieve()
+                .toEntity(Object.class)
+                .onErrorMap(throwable -> handleDmiClientException(throwable, operationType.getOperationName()));
+    }
+
+    /**
+     * Retrieves the health status of the DMI plugin.
+     * This method performs an HTTP GET request to the DMI health check endpoint specified by the URL template
+     * parameters. If the response status code indicates a client error (4xx) or a server error (5xx), it logs a warning
+     * and returns an empty Mono. In case of an error during the request, it logs the exception and returns a default
+     * value of "NOT_SPECIFIED". If the response body contains a JSON node with a "status" field, the value of this
+     * field is returned.
+     *
+     * @param urlTemplateParameters the URL template parameters for the DMI health check endpoint
+     * @return a Mono emitting the health status as a String, or "NOT_SPECIFIED" if an error occurs
+     */
+    public Mono<String> getDmiHealthStatus(final UrlTemplateParameters urlTemplateParameters) {
+        return healthChecksWebClient.get()
+                .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
+                .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION))
+                .retrieve()
+                .bodyToMono(JsonNode.class)
+                .map(responseHealthStatus -> responseHealthStatus.path("status").asText())
+                .onErrorResume(Exception.class, ex -> {
+                    log.warn("Failed to retrieve health status from {}. Status: {}",
+                            urlTemplateParameters.urlTemplate(), ex.getMessage());
+                    return Mono.empty();
+                })
+                .defaultIfEmpty(NOT_SPECIFIED);
+    }
+
+    /**
+     * Retrieves the status of a data job from the DMI service.
+     *
+     * @param urlTemplateParameters   The URL template parameters for the DMI data job status endpoint.
+     * @param authorization           The authorization token to be added to the request headers.
+     * @return A Mono emitting the status of the data job as a String.
+     * @throws DmiClientRequestException If there is an error during the DMI request.
+     */
+    public Mono<String> getDataJobStatus(final UrlTemplateParameters urlTemplateParameters,
+                                         final String authorization) {
+
+        return dataServicesWebClient.get()
+                .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
+                .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization))
+                .retrieve()
+                .bodyToMono(JsonNode.class)
+                .map(jsonNode -> jsonNode.path("status").asText())
+                .onErrorMap(throwable -> handleDmiClientException(throwable, OperationType.READ.getOperationName()));
+    }
+
+    /**
+     * Retrieves the result of a data job from the DMI service.
+     *
+     * @param urlTemplateParameters   The URL template parameters for the DMI data job status endpoint.
+     * @param authorization           The authorization token to be added to the request headers.
+     * @return A Mono emitting the result of the data job as a String.
+     * @throws DmiClientRequestException If there is an error during the DMI request.
+     */
+    public Mono<String> getDataJobResult(final UrlTemplateParameters urlTemplateParameters,
+                                         final String authorization) {
+        return dataServicesWebClient.get()
+                                        .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
+                                        .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization))
+                                        .retrieve().bodyToMono(String.class)
+                                        .onErrorMap(throwable -> handleDmiClientException(throwable,
+                                                                 OperationType.READ.getOperationName()));
+    }
+
+    private WebClient getWebClient(final RequiredDmiService requiredDmiService) {
+        return requiredDmiService.equals(RequiredDmiService.DATA) ? dataServicesWebClient : modelServicesWebClient;
+    }
+
+    private void configureHttpHeaders(final HttpHeaders httpHeaders, final String authorization) {
+        if (dmiProperties.isDmiBasicAuthEnabled()) {
+            httpHeaders.setBasicAuth(dmiProperties.getAuthUsername(), dmiProperties.getAuthPassword());
+        } else if (authorization != null && authorization.toLowerCase(Locale.getDefault()).startsWith("bearer ")) {
+            httpHeaders.add(HttpHeaders.AUTHORIZATION, authorization);
+        }
+    }
+
+    private DmiClientRequestException handleDmiClientException(final Throwable throwable, final String operationType) {
+        if (throwable instanceof WebClientResponseException webClientResponseException) {
+            if (webClientResponseException.getStatusCode().isSameCodeAs(REQUEST_TIMEOUT)) {
+                throw new DmiClientRequestException(webClientResponseException.getStatusCode().value(),
+                        webClientResponseException.getMessage(),
+                        jsonObjectMapper.asJsonString(webClientResponseException.getResponseBodyAsString()),
+                        DMI_SERVICE_NOT_RESPONDING);
+            }
+            throw new DmiClientRequestException(webClientResponseException.getStatusCode().value(),
+                    webClientResponseException.getMessage(),
+                    jsonObjectMapper.asJsonString(webClientResponseException.getResponseBodyAsString()),
+                    UNABLE_TO_READ_RESOURCE_DATA);
+
+        }
+        final String exceptionMessage = "Unable to " + operationType + " resource data.";
+        if (throwable instanceof WebClientRequestException webClientRequestException) {
+            throw new DmiClientRequestException(HttpStatus.SERVICE_UNAVAILABLE.value(),
+                    webClientRequestException.getMessage(),
+                    exceptionMessage, DMI_SERVICE_NOT_RESPONDING);
+        }
+        if (throwable instanceof HttpServerErrorException httpServerErrorException) {
+            throw new DmiClientRequestException(httpServerErrorException.getStatusCode().value(), exceptionMessage,
+                    httpServerErrorException.getResponseBodyAsString(), DMI_SERVICE_NOT_RESPONDING);
+        }
+        throw new DmiClientRequestException(INTERNAL_SERVER_ERROR.value(), exceptionMessage, throwable.getMessage(),
+                UNKNOWN_ERROR);
+    }
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils;
+package org.onap.cps.ncmp.impl.dmi;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -27,8 +27,8 @@ import java.util.Map;
 import java.util.stream.Collectors;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
-import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.models.RequiredDmiService;
 
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class DmiServiceNameOrganizer {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfiguration.java
new file mode 100644 (file)
index 0000000..4134a56
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.dmi;
+
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.config.DmiHttpClientConfig;
+import org.onap.cps.ncmp.impl.utils.http.WebClientConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@Configuration
+@RequiredArgsConstructor
+public class DmiWebClientsConfiguration extends WebClientConfiguration {
+
+    private final DmiHttpClientConfig dmiHttpClientConfig;
+
+    /**
+     * Configures and creates a web client bean for DMI data services.
+     *
+     * @param webClientBuilder The builder instance to create the WebClient.
+     * @return a WebClient instance configured for data services.
+     */
+    @Bean
+    public WebClient dataServicesWebClient(final WebClient.Builder webClientBuilder) {
+        return configureWebClient(webClientBuilder, dmiHttpClientConfig.getDataServices());
+    }
+
+    /**
+     * Configures and creates a web client bean for DMI model services.
+     *
+     * @param webClientBuilder The builder instance to create the WebClient.
+     * @return a WebClient instance configured for model services.
+     */
+    @Bean
+    public WebClient modelServicesWebClient(final WebClient.Builder webClientBuilder) {
+        return configureWebClient(webClientBuilder, dmiHttpClientConfig.getModelServices());
+    }
+
+    /**
+     * Configures and creates a web client bean for DMI health check services.
+     *
+     * @param webClientBuilder The builder instance to create the WebClient.
+     * @return a WebClient instance configured for health check services.
+     */
+    @Bean
+    public WebClient healthChecksWebClient(final WebClient.Builder webClientBuilder) {
+        return configureWebClient(webClientBuilder, dmiHttpClientConfig.getHealthCheckServices());
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java
new file mode 100644 (file)
index 0000000..3600d6d
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * ============LICENSE_START========================================================
+ * Copyright (c) 2024 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.impl.inventory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class AlternateIdChecker {
+
+    public enum Operation {
+        CREATE, UPDATE
+    }
+
+    private final InventoryPersistence inventoryPersistence;
+
+    private static final String NO_CURRENT_ALTERNATE_ID = "";
+
+    /**
+     * Check all alternate ids of a batch of cm handles.
+     * Includes cross-checks in the batch itself for duplicates. Only the first entry encountered wil be accepted.
+     *
+     * @param newNcmpServiceCmHandles the proposed new cm handles
+     * @param operation type of operation being executed
+     * @return collection of cm handles ids which are acceptable
+     */
+    public Collection<String> getIdsOfCmHandlesWithRejectedAlternateId(
+                                    final Collection<NcmpServiceCmHandle> newNcmpServiceCmHandles,
+                                    final Operation operation) {
+        final Set<String> assignedAlternateIds = getAlternateIdsAlreadyInDb(newNcmpServiceCmHandles);
+        final Collection<String> rejectedCmHandleIds = new ArrayList<>();
+        for (final NcmpServiceCmHandle ncmpServiceCmHandle : newNcmpServiceCmHandles) {
+            final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
+            final String proposedAlternateId = ncmpServiceCmHandle.getAlternateId();
+            if (isProposedAlternateIdAcceptable(proposedAlternateId, operation, assignedAlternateIds, cmHandleId)) {
+                assignedAlternateIds.add(proposedAlternateId);
+            } else {
+                rejectedCmHandleIds.add(cmHandleId);
+            }
+        }
+        return rejectedCmHandleIds;
+    }
+
+    private boolean isProposedAlternateIdAcceptable(final String proposedAlternateId, final Operation operation,
+                                                    final Set<String> assignedAlternateIds, final String cmHandleId) {
+        if (StringUtils.isBlank(proposedAlternateId)) {
+            return true;
+        }
+        final String currentAlternateId = getCurrentAlternateId(operation, cmHandleId);
+        if (currentAlternateId.equals(proposedAlternateId)) {
+            return true;
+        }
+        if (StringUtils.isNotBlank(currentAlternateId)) {
+            log.warn("Alternate id update ignored, cannot update cm handle {}, already has an alternate id of {}",
+                    cmHandleId, currentAlternateId);
+            return false;
+        }
+        if (assignedAlternateIds.contains(proposedAlternateId)) {
+            log.warn("Alternate id update ignored, cannot update cm handle {}, alternate id is already "
+                    + "assigned to a different cm handle", cmHandleId);
+            return false;
+        }
+        return true;
+    }
+
+    private Set<String> getAlternateIdsAlreadyInDb(final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles) {
+        final Set<String> alternateIdsToCheck = ncmpServiceCmHandles.stream()
+                .map(NcmpServiceCmHandle::getAlternateId)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toSet());
+        return inventoryPersistence.getCmHandleDataNodesByAlternateIds(alternateIdsToCheck).stream()
+                .map(dataNode -> (String) dataNode.getLeaves().get("alternate-id"))
+                .collect(Collectors.toSet());
+    }
+
+    private String getCurrentAlternateId(final Operation operation, final String cmHandleId) {
+        if (operation == Operation.UPDATE) {
+            try {
+                return inventoryPersistence.getYangModelCmHandle(cmHandleId).getAlternateId();
+            } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
+                // work with blank current alternate id
+            }
+        }
+        return NO_CURRENT_ALTERNATE_ID;
+    }
+
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils;
+package org.onap.cps.ncmp.impl.inventory;
 
 import com.google.common.base.Strings;
 import java.util.Collection;
@@ -26,12 +26,12 @@ import java.util.Map;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters;
 import org.onap.cps.spi.exceptions.DataValidationException;
 
 @Slf4j
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class RestQueryParametersValidator {
+public class CmHandleQueryParametersValidator {
 
     /**
      * Validate query parameters.
@@ -55,7 +55,7 @@ public class RestQueryParametersValidator {
                                 "Empty 'conditionsParameters' - please supply a valid condition parameter.");
                     }
                     cmHandleQueryParameter.getConditionParameters().forEach(
-                            RestQueryParametersValidator::validateConditionParameter
+                            CmHandleQueryParametersValidator::validateConditionParameter
                     );
                 }
         );
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
 
-public interface CmHandleQueries {
+public interface CmHandleQueryService {
 
     /**
-     * Query CmHandles based on additional (private) properties.
+     * Query Cm Handles based on additional (private) properties.
      *
      * @param additionalPropertyQueryPairs private properties for query
-     * @return Ids of CmHandles which have these private properties
+     * @return Ids of Cm Handles which have these private properties
      */
     Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs);
 
     /**
-     * Query CmHandles based on public properties.
+     * Query Cm Handles based on public properties.
      *
      * @param publicPropertyQueryPairs public properties for query
      * @return CmHandles which have these public properties
@@ -45,10 +45,10 @@ public interface CmHandleQueries {
     Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs);
 
     /**
-     * Query CmHandles based on Trust Level.
+     * Query Cm Handles based on Trust Level.
      *
      * @param trustLevelPropertyQueryPairs trust level properties for query
-     * @return CmHandles which have desired trust level
+     * @return Ids of Cm Handles which have desired trust level
      */
     Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs);
 
@@ -56,9 +56,9 @@ public interface CmHandleQueries {
      * Method which returns cm handles by the cm handles state.
      *
      * @param cmHandleState cm handle state
-     * @return a list of cm handles
+     * @return a list of data nodes representing the cm handles.
      */
-    List<DataNode> queryCmHandlesByState(CmHandleState cmHandleState);
+    Collection<DataNode> queryCmHandlesByState(CmHandleState cmHandleState);
 
     /**
      * Method to return data nodes with ancestor representing the cm handles.
@@ -66,8 +66,7 @@ public interface CmHandleQueries {
      * @param cpsPath cps path for which the cmHandle is requested
      * @return a list of data nodes representing the cm handles.
      */
-    List<DataNode> queryCmHandleAncestorsByCpsPath(String cpsPath,
-                                                   FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> queryCmHandleAncestorsByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
     /**
      * Method to return data nodes representing the cm handles.
@@ -75,7 +74,7 @@ public interface CmHandleQueries {
      * @param cpsPath cps path for which the cmHandle is requested
      * @return a list of data nodes representing the cm handles.
      */
-    List<DataNode> queryNcmpRegistryByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> queryNcmpRegistryByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
     /**
      * Method to check the state of a cm handle with given id.
@@ -90,15 +89,16 @@ public interface CmHandleQueries {
      * Method which returns cm handles by the operational sync state of cm handle.
      *
      * @param dataStoreSyncState sync state
-     * @return a list of cm handles
+     * @return a list of data nodes representing the cm handles.
      */
-    List<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState);
+    Collection<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState);
 
     /**
      * Get all cm handles ids by DMI plugin identifier.
      *
      * @param dmiPluginIdentifier DMI plugin identifier
-     * @return collection of cm handles
+     * @return collection of cm handle ids
      */
     Collection<String> getCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier);
+
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory;
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
 
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig;
-import org.onap.cps.ncmp.api.impl.inventory.enums.PropertyType;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
-import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsQueryService;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
+import org.onap.cps.impl.utils.CpsValidator;
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.ModelledDmiServiceLeaves;
+import org.onap.cps.ncmp.impl.inventory.models.PropertyType;
+import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelCacheConfig;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
 @RequiredArgsConstructor
 @Component
-public class CmHandleQueriesImpl implements CmHandleQueries {
+public class CmHandleQueryServiceImpl implements CmHandleQueryService {
 
     private static final String DESCENDANT_PATH = "//";
     private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles";
-    private final CpsDataPersistenceService cpsDataPersistenceService;
+    private final CpsDataService cpsDataService;
+    private final CpsQueryService cpsQueryService;
 
     @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_DMI_PLUGIN)
     private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
@@ -79,21 +83,24 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
     }
 
     @Override
-    public List<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) {
+    public Collection<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) {
         return queryCmHandleAncestorsByCpsPath("//state[@cm-handle-state=\"" + cmHandleState + "\"]",
             INCLUDE_ALL_DESCENDANTS);
     }
 
     @Override
-    public List<DataNode> queryNcmpRegistryByCpsPath(final String cpsPath,
+    public Collection<DataNode> queryNcmpRegistryByCpsPath(final String cpsPath,
                                                      final FetchDescendantsOption fetchDescendantsOption) {
-        return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                cpsPath, fetchDescendantsOption);
+        return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath,
+                fetchDescendantsOption);
     }
 
     @Override
-    public List<DataNode> queryCmHandleAncestorsByCpsPath(final String cpsPath,
+    public Collection<DataNode> queryCmHandleAncestorsByCpsPath(final String cpsPath,
                                                           final FetchDescendantsOption fetchDescendantsOption) {
+        if (CpsPathUtil.getCpsPathQuery(cpsPath).getXpathPrefix().endsWith("/cm-handles")) {
+            return queryNcmpRegistryByCpsPath(cpsPath, fetchDescendantsOption);
+        }
         return queryNcmpRegistryByCpsPath(cpsPath + ANCESTOR_CM_HANDLES, fetchDescendantsOption);
     }
 
@@ -105,7 +112,7 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
     }
 
     @Override
-    public List<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) {
+    public Collection<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) {
         return queryCmHandleAncestorsByCpsPath("//state/datastores" + "/operational[@sync-state=\""
                 + dataStoreSyncState + "\"]", FetchDescendantsOption.OMIT_DESCENDANTS);
     }
@@ -174,9 +181,9 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
         return cmHandleIds;
     }
 
-    private List<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier,
-                                                                           final String dmiProperty) {
-        return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+    private Collection<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier,
+                                                                                 final String dmiProperty) {
+        return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@" + dmiProperty + "='" + dmiPluginIdentifier + "']",
                 OMIT_DESCENDANTS);
     }
@@ -184,7 +191,7 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
     private DataNode getCmHandleState(final String cmHandleId) {
         cpsValidator.validateNameCharacters(cmHandleId);
         final String xpath = NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']/state";
-        return cpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+        return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 xpath, OMIT_DESCENDANTS).iterator().next();
     }
 }
@@ -1,7 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021 highstreet technologies GmbH
- *  Modifications Copyright (C) 2021-2024 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl;
+package org.onap.cps.ncmp.impl.inventory;
 
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED;
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND;
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_READY;
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST;
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID;
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCmHandleQueryParameters;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE;
 
 import com.google.common.collect.Lists;
 import com.hazelcast.map.IMap;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -49,77 +48,52 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.cps.api.CpsDataService;
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig;
-import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateUtils;
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleOperationsUtils;
-import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
-import org.onap.cps.ncmp.api.impl.operations.OperationType;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager;
-import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker;
-import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions;
-import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
-import org.onap.cps.ncmp.api.models.CmResourceAddress;
-import org.onap.cps.ncmp.api.models.DataOperationRequest;
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
-import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder;
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration;
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.inventory.sync.ModuleOperationsUtils;
+import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler;
+import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
-import org.onap.cps.spi.model.ModuleDefinition;
-import org.onap.cps.spi.model.ModuleReference;
-import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 
 @Slf4j
 @Service
 @RequiredArgsConstructor
-public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService {
+public class CmHandleRegistrationService {
 
     private static final int DELETE_BATCH_SIZE = 100;
-    private final JsonObjectMapper jsonObjectMapper;
-    private final DmiDataOperations dmiDataOperations;
-    private final NetworkCmProxyDataServicePropertyHandler networkCmProxyDataServicePropertyHandler;
+
+    private final CmHandleRegistrationServicePropertyHandler cmHandleRegistrationServicePropertyHandler;
     private final InventoryPersistence inventoryPersistence;
-    private final CmHandleQueries cmHandleQueries;
-    private final NetworkCmProxyCmHandleQueryService networkCmProxyCmHandleQueryService;
-    private final LcmEventsCmHandleStateHandler lcmEventsCmHandleStateHandler;
     private final CpsDataService cpsDataService;
+    private final LcmEventsCmHandleStateHandler lcmEventsCmHandleStateHandler;
     private final IMap<String, Object> moduleSyncStartedOnCmHandles;
-
-    @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_DMI_PLUGIN)
-    private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
-
     private final TrustLevelManager trustLevelManager;
     private final AlternateIdChecker alternateIdChecker;
 
-    @Override
+    /**
+     * Registration of Created, Removed, Updated or Upgraded CM Handles.
+     *
+     * @param dmiPluginRegistration Dmi Plugin Registration details
+     * @return dmiPluginRegistrationResponse
+     */
     public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(
         final DmiPluginRegistration dmiPluginRegistration) {
 
         dmiPluginRegistration.validateDmiPluginRegistration();
         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse();
 
-        setTrustLevelPerDmiPlugin(dmiPluginRegistration);
+        trustLevelManager.registerDmiPlugin(dmiPluginRegistration);
 
         processRemovedCmHandles(dmiPluginRegistration, dmiPluginRegistrationResponse);
 
@@ -132,95 +106,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         return dmiPluginRegistrationResponse;
     }
 
-    @Override
-    public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
-                                             final String optionsParamInQuery,
-                                             final String topicParamInQuery,
-                                             final String requestId,
-                                             final String authorization) {
-        final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmResourceAddress,
-            optionsParamInQuery,
-            topicParamInQuery,
-            requestId,
-            authorization);
-        return responseEntity.getBody();
-    }
-
-    @Override
-    public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
-                                             final FetchDescendantsOption fetchDescendantsOption) {
-        return cpsDataService.getDataNodes(cmResourceAddress.datastoreName(),
-                                           cmResourceAddress.cmHandleId(),
-                                           cmResourceAddress.resourceIdentifier(),
-                                           fetchDescendantsOption).iterator().next();
-    }
-
-    @Override
-    public void executeDataOperationForCmHandles(final String topicParamInQuery,
-                                                 final DataOperationRequest dataOperationRequest,
-                                                 final String requestId,
-                                                 final String authorization) {
-        dmiDataOperations.requestResourceDataFromDmi(topicParamInQuery, dataOperationRequest, requestId,
-                authorization);
-    }
-
-    @Override
-    public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleId,
-                                                                 final String resourceIdentifier,
-                                                                 final OperationType operationType,
-                                                                 final String requestData,
-                                                                 final String dataType,
-                                                                 final String authorization) {
-        return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier,
-            operationType, requestData, dataType, authorization);
-    }
-
-    @Override
-    public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
-        return inventoryPersistence.getYangResourcesModuleReferences(cmHandleId);
-    }
-
-    @Override
-    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) {
-        return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId);
-    }
-
-    @Override
-    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleId,
-                                                                                final String moduleName,
-                                                                                final String moduleRevision) {
-        return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision);
-    }
-
-    /**
-     * Retrieve cm handles with details for the given query parameters.
-     *
-     * @param cmHandleQueryApiParameters cm handle query parameters
-     * @return cm handles with details
-     */
-    @Override
-    public Collection<NcmpServiceCmHandle> executeCmHandleSearch(
-        final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
-            cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
-        validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
-        return networkCmProxyCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters);
-    }
-
-    /**
-     * Retrieve cm handle ids for the given query parameters.
-     *
-     * @param cmHandleQueryApiParameters cm handle query parameters
-     * @return cm handle ids
-     */
-    @Override
-    public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
-            cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
-        validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
-        return networkCmProxyCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters);
-    }
-
     /**
      * Set the data sync enabled flag, along with the data sync state
      * based on the data sync enabled boolean for the cm handle id provided.
@@ -228,7 +113,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * @param cmHandleId                 cm handle id
      * @param dataSyncEnabledTargetValue data sync enabled flag
      */
-    @Override
     public void setDataSyncEnabled(final String cmHandleId, final Boolean dataSyncEnabledTargetValue) {
         final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId);
         if (dataSyncEnabledTargetValue.equals(compositeState.getDataSyncEnabled())) {
@@ -252,70 +136,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    /**
-     * Get all cm handle IDs by DMI plugin identifier.
-     *
-     * @param dmiPluginIdentifier DMI plugin identifier
-     * @return set of cm handle IDs
-     */
-    @Override
-    public Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) {
-        return cmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier);
-    }
-
-    /**
-     * Get all cm handle IDs by various properties.
-     *
-     * @param cmHandleQueryServiceParameters cm handle query parameters
-     * @return set of cm handle IDs
-     */
-    @Override
-    public Collection<String> executeCmHandleIdSearchForInventory(
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-        validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES);
-        return networkCmProxyCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters);
-    }
-
-    /**
-     * Retrieve cm handle details for a given cm handle.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return cm handle details
-     */
-    @Override
-    public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) {
-        return YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle(
-            inventoryPersistence.getYangModelCmHandle(cmHandleId));
-    }
-
-    /**
-     * Get cm handle public properties for a given cm handle id.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return cm handle public properties
-     */
-    @Override
-    public Map<String, String> getCmHandlePublicProperties(final String cmHandleId) {
-        final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
-        final List<YangModelCmHandle.Property> yangModelPublicProperties = yangModelCmHandle.getPublicProperties();
-        final Map<String, String> cmHandlePublicProperties = new HashMap<>();
-        YangDataConverter.asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties);
-        return cmHandlePublicProperties;
-    }
-
-    /**
-     * Get cm handle composite state for a given cm handle id.
-     *
-     * @param cmHandleId cm handle identifier
-     * @return cm handle state
-     */
-    @Override
-    public CompositeState getCmHandleCompositeState(final String cmHandleId) {
-        return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState();
-    }
-
     protected void processRemovedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
-                                         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+                                           final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
         final List<String> tobeRemovedCmHandleIds = dmiPluginRegistration.getRemovedCmHandles();
         final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
             new ArrayList<>(tobeRemovedCmHandleIds.size());
@@ -326,7 +148,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         final Set<String> notDeletedCmHandles = new HashSet<>();
         for (final List<String> tobeRemovedCmHandleBatch : Lists.partition(tobeRemovedCmHandleIds, DELETE_BATCH_SIZE)) {
             try {
-                batchDeleteCmHandlesFromDbAndModuleSyncMap(tobeRemovedCmHandleBatch);
+                batchDeleteCmHandlesFromDbAndCaches(tobeRemovedCmHandleBatch);
                 tobeRemovedCmHandleBatch.forEach(cmHandleId ->
                     cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId)));
 
@@ -348,7 +170,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     protected void processCreatedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
-                                         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+                                           final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
         final List<NcmpServiceCmHandle> ncmpServiceCmHandles = dmiPluginRegistration.getCreatedCmHandles();
         final List<CmHandleRegistrationResponse> failedCmHandleRegistrationResponses = new ArrayList<>();
 
@@ -378,8 +200,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     protected void processUpdatedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
-                                         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
-        dmiPluginRegistrationResponse.setUpdatedCmHandles(networkCmProxyDataServicePropertyHandler
+                                           final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+        dmiPluginRegistrationResponse.setUpdatedCmHandles(cmHandleRegistrationServicePropertyHandler
             .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
     }
 
@@ -405,67 +227,24 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                     }
                 } else {
                     cmHandleUpgradeResponses.add(
-                            CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_READY));
+                        CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_READY));
                 }
             } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
                 log.error("Unable to find data node for cm handle id : {} , caused by : {}",
-                        cmHandleId, dataNodeNotFoundException.getMessage());
+                    cmHandleId, dataNodeNotFoundException.getMessage());
                 cmHandleUpgradeResponses.add(
-                        CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND));
+                    CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND));
             } catch (final DataValidationException dataValidationException) {
                 log.error("Unable to upgrade cm handle id: {}, caused by : {}",
-                        cmHandleId, dataValidationException.getMessage());
+                    cmHandleId, dataValidationException.getMessage());
                 cmHandleUpgradeResponses.add(
-                        CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLE_INVALID_ID));
+                    CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLE_INVALID_ID));
             }
         }
         cmHandleUpgradeResponses.addAll(upgradeCmHandles(acceptedCmHandleStatePerCmHandle));
         dmiPluginRegistrationResponse.setUpgradedCmHandles(cmHandleUpgradeResponses);
     }
 
-    private Collection<String> checkAlternateIds(
-        final List<NcmpServiceCmHandle> cmHandlesToBeCreated,
-        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses) {
-        final Collection<String> rejectedCmHandleIds = alternateIdChecker
-            .getIdsOfCmHandlesWithRejectedAlternateId(cmHandlesToBeCreated, AlternateIdChecker.Operation.CREATE);
-        cmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse.createFailureResponses(
-            rejectedCmHandleIds, ALTERNATE_ID_ALREADY_ASSOCIATED));
-        return rejectedCmHandleIds;
-    }
-
-    private List<String> persistCmHandlesWithState(final DmiPluginRegistration dmiPluginRegistration,
-                                                   final DmiPluginRegistrationResponse dmiPluginRegistrationResponse,
-                                                   final List<NcmpServiceCmHandle> cmHandlesToBeCreated,
-                                                   final Collection<String> rejectedCmHandleIds) {
-        final List<String> succeededCmHandleIds = new ArrayList<>(cmHandlesToBeCreated.size());
-        final List<YangModelCmHandle> yangModelCmHandlesToRegister = new ArrayList<>(cmHandlesToBeCreated.size());
-        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
-            new ArrayList<>(cmHandlesToBeCreated.size());
-        for (final NcmpServiceCmHandle ncmpServiceCmHandle: cmHandlesToBeCreated) {
-            if (!rejectedCmHandleIds.contains(ncmpServiceCmHandle.getCmHandleId())) {
-                yangModelCmHandlesToRegister.add(getYangModelCmHandle(dmiPluginRegistration, ncmpServiceCmHandle));
-                cmHandleRegistrationResponses.add(
-                    CmHandleRegistrationResponse.createSuccessResponse(ncmpServiceCmHandle.getCmHandleId()));
-                succeededCmHandleIds.add(ncmpServiceCmHandle.getCmHandleId());
-            }
-        }
-        lcmEventsCmHandleStateHandler.initiateStateAdvised(yangModelCmHandlesToRegister);
-        dmiPluginRegistrationResponse.setCreatedCmHandles(cmHandleRegistrationResponses);
-        return succeededCmHandleIds;
-    }
-
-    private YangModelCmHandle getYangModelCmHandle(final DmiPluginRegistration dmiPluginRegistration,
-                                                   final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        return YangModelCmHandle.toYangModelCmHandle(
-            dmiPluginRegistration.getDmiPlugin(),
-            dmiPluginRegistration.getDmiDataPlugin(),
-            dmiPluginRegistration.getDmiModelPlugin(),
-            ncmpServiceCmHandle,
-            ncmpServiceCmHandle.getModuleSetTag(),
-            ncmpServiceCmHandle.getAlternateId(),
-            ncmpServiceCmHandle.getDataProducerIdentifier());
-    }
-
     private void processTrustLevels(final Collection<NcmpServiceCmHandle> cmHandlesToBeCreated,
                                     final Collection<String> succeededCmHandleIds) {
         final Map<String, TrustLevel> initialTrustLevelPerCmHandleId = new HashMap<>(cmHandlesToBeCreated.size());
@@ -475,7 +254,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                     ncmpServiceCmHandle.getRegistrationTrustLevel());
             }
         }
-        trustLevelManager.handleInitialRegistrationOfTrustLevels(initialTrustLevelPerCmHandleId);
+        trustLevelManager.registerCmHandles(initialTrustLevelPerCmHandleId);
     }
 
     private static boolean moduleUpgradeCanBeSkipped(final YangModelCmHandle yangModelCmHandle,
@@ -489,14 +268,14 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     private static void updateYangModelCmHandleForUpgrade(final YangModelCmHandle yangModelCmHandle,
                                                           final String upgradedModuleSetTag) {
         final String lockReasonWithModuleSetTag = String.format(ModuleOperationsUtils.MODULE_SET_TAG_MESSAGE_FORMAT,
-                upgradedModuleSetTag);
+            upgradedModuleSetTag);
         yangModelCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.READY)
-                .withLockReason(MODULE_UPGRADE, lockReasonWithModuleSetTag).build());
+            .withLockReason(MODULE_UPGRADE, lockReasonWithModuleSetTag).build());
     }
 
     private CmHandleRegistrationResponse deleteCmHandleAndGetCmHandleRegistrationResponse(final String cmHandleId) {
         try {
-            deleteCmHandleFromDbAndModuleSyncMap(cmHandleId);
+            deleteCmHandleFromDbAndCaches(cmHandleId);
             return CmHandleRegistrationResponse.createSuccessResponse(cmHandleId);
         } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
             log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}",
@@ -519,15 +298,17 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
     }
 
-    private void deleteCmHandleFromDbAndModuleSyncMap(final String cmHandleId) {
+    private void deleteCmHandleFromDbAndCaches(final String cmHandleId) {
         inventoryPersistence.deleteSchemaSetWithCascade(cmHandleId);
         inventoryPersistence.deleteDataNode(NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']");
+        trustLevelManager.removeCmHandles(Collections.singleton(cmHandleId));
         removeDeletedCmHandleFromModuleSyncMap(cmHandleId);
     }
 
-    private void batchDeleteCmHandlesFromDbAndModuleSyncMap(final Collection<String> cmHandleIds) {
+    private void batchDeleteCmHandlesFromDbAndCaches(final Collection<String> cmHandleIds) {
         inventoryPersistence.deleteSchemaSetsWithCascade(cmHandleIds);
         inventoryPersistence.deleteDataNodes(mapCmHandleIdsToXpaths(cmHandleIds));
+        trustLevelManager.removeCmHandles(cmHandleIds);
         cmHandleIds.forEach(this::removeDeletedCmHandleFromModuleSyncMap);
     }
 
@@ -561,12 +342,48 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         return cmHandleStatePerCmHandle.keySet().stream().map(YangModelCmHandle::getId).toList();
     }
 
-    private void setTrustLevelPerDmiPlugin(final DmiPluginRegistration dmiPluginRegistration) {
-        if (DmiPluginRegistration.isNullEmptyOrBlank(dmiPluginRegistration.getDmiDataPlugin())) {
-            trustLevelPerDmiPlugin.put(dmiPluginRegistration.getDmiPlugin(), TrustLevel.COMPLETE);
-        } else {
-            trustLevelPerDmiPlugin.put(dmiPluginRegistration.getDmiDataPlugin(), TrustLevel.COMPLETE);
+    private Collection<String> checkAlternateIds(
+        final List<NcmpServiceCmHandle> cmHandlesToBeCreated,
+        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses) {
+        final Collection<String> rejectedCmHandleIds = alternateIdChecker
+            .getIdsOfCmHandlesWithRejectedAlternateId(cmHandlesToBeCreated, AlternateIdChecker.Operation.CREATE);
+        cmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse.createFailureResponses(
+            rejectedCmHandleIds, ALTERNATE_ID_ALREADY_ASSOCIATED));
+        return rejectedCmHandleIds;
+    }
+
+    private List<String> persistCmHandlesWithState(final DmiPluginRegistration dmiPluginRegistration,
+                                                   final DmiPluginRegistrationResponse dmiPluginRegistrationResponse,
+                                                   final List<NcmpServiceCmHandle> cmHandlesToBeCreated,
+                                                   final Collection<String> rejectedCmHandleIds) {
+        final List<String> succeededCmHandleIds = new ArrayList<>(cmHandlesToBeCreated.size());
+        final List<YangModelCmHandle> yangModelCmHandlesToRegister = new ArrayList<>(cmHandlesToBeCreated.size());
+        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
+            new ArrayList<>(cmHandlesToBeCreated.size());
+        for (final NcmpServiceCmHandle ncmpServiceCmHandle: cmHandlesToBeCreated) {
+            if (!rejectedCmHandleIds.contains(ncmpServiceCmHandle.getCmHandleId())) {
+                yangModelCmHandlesToRegister.add(getYangModelCmHandle(dmiPluginRegistration, ncmpServiceCmHandle));
+                cmHandleRegistrationResponses.add(
+                    CmHandleRegistrationResponse.createSuccessResponse(ncmpServiceCmHandle.getCmHandleId()));
+                succeededCmHandleIds.add(ncmpServiceCmHandle.getCmHandleId());
+            }
         }
+        lcmEventsCmHandleStateHandler.initiateStateAdvised(yangModelCmHandlesToRegister);
+        dmiPluginRegistrationResponse.setCreatedCmHandles(cmHandleRegistrationResponses);
+        return succeededCmHandleIds;
     }
 
+    private YangModelCmHandle getYangModelCmHandle(final DmiPluginRegistration dmiPluginRegistration,
+                                                   final NcmpServiceCmHandle ncmpServiceCmHandle) {
+        return YangModelCmHandle.toYangModelCmHandle(
+            dmiPluginRegistration.getDmiPlugin(),
+            dmiPluginRegistration.getDmiDataPlugin(),
+            dmiPluginRegistration.getDmiModelPlugin(),
+            ncmpServiceCmHandle,
+            ncmpServiceCmHandle.getModuleSetTag(),
+            ncmpServiceCmHandle.getAlternateId(),
+            ncmpServiceCmHandle.getDataProducerIdentifier());
+    }
+
+
 }
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl;
+package org.onap.cps.ncmp.impl.inventory;
 
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED;
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND;
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID;
-import static org.onap.cps.ncmp.api.impl.NetworkCmProxyDataServicePropertyHandler.PropertyType.DMI_PROPERTY;
-import static org.onap.cps.ncmp.api.impl.NetworkCmProxyDataServicePropertyHandler.PropertyType.PUBLIC_PROPERTY;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
+import static org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.DMI_PROPERTY;
+import static org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.PUBLIC_PROPERTY;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
 
 import com.google.common.collect.ImmutableMap;
 import java.time.OffsetDateTime;
@@ -44,27 +44,26 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.onap.cps.api.CpsDataService;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
+import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
 
 @Slf4j
 @Service
 @RequiredArgsConstructor
 //Accepting the security hotspot as the string checked is generated from inside code and not user input.
 @SuppressWarnings("squid:S5852")
-public class NetworkCmProxyDataServicePropertyHandler {
+public class CmHandleRegistrationServicePropertyHandler {
 
     private final InventoryPersistence inventoryPersistence;
     private final CpsDataService cpsDataService;
@@ -113,8 +112,7 @@ public class NetworkCmProxyDataServicePropertyHandler {
 
     private void processUpdates(final DataNode existingCmHandleDataNode,
                                 final NcmpServiceCmHandle updatedNcmpServiceCmHandle) {
-        setAndUpdateCmHandleField(
-            updatedNcmpServiceCmHandle.getCmHandleId(), "alternate-id", updatedNcmpServiceCmHandle.getAlternateId());
+        updateAlternateId(updatedNcmpServiceCmHandle);
         updateDataProducerIdentifier(existingCmHandleDataNode, updatedNcmpServiceCmHandle);
         if (!updatedNcmpServiceCmHandle.getPublicProperties().isEmpty()) {
             updateProperties(existingCmHandleDataNode, PUBLIC_PROPERTY,
@@ -125,18 +123,27 @@ public class NetworkCmProxyDataServicePropertyHandler {
         }
     }
 
+    private void updateAlternateId(final NcmpServiceCmHandle ncmpServiceCmHandle) {
+        final String newAlternateId = ncmpServiceCmHandle.getAlternateId();
+        if (StringUtils.isNotBlank(newAlternateId)) {
+            setAndUpdateCmHandleField(ncmpServiceCmHandle.getCmHandleId(), "alternate-id", newAlternateId);
+        }
+    }
+
     private void updateDataProducerIdentifier(final DataNode cmHandleDataNode,
                                               final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final String newDataProducerIdentifier = ncmpServiceCmHandle.getDataProducerIdentifier();
-        if (StringUtils.hasText(newDataProducerIdentifier)) {
-            final YangModelCmHandle yangModelCmHandle =
-                YangDataConverter.convertCmHandleToYangModel(cmHandleDataNode);
+        if (StringUtils.isNotBlank(newDataProducerIdentifier)) {
+            final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(cmHandleDataNode);
             final String existingDataProducerIdentifier = yangModelCmHandle.getDataProducerIdentifier();
-            if (StringUtils.hasText(existingDataProducerIdentifier)) {
+            if (StringUtils.isNotBlank(existingDataProducerIdentifier)) {
                 if (!existingDataProducerIdentifier.equals(newDataProducerIdentifier)) {
                     log.warn("Unable to update dataProducerIdentifier for cmHandle {}. "
                             + "Value for dataProducerIdentifier has been set previously.",
                         ncmpServiceCmHandle.getCmHandleId());
+                } else {
+                    log.debug("dataProducerIdentifier for cmHandle {} is already set to {}.",
+                        ncmpServiceCmHandle.getCmHandleId(), newDataProducerIdentifier);
                 }
             } else {
                 setAndUpdateCmHandleField(
@@ -222,7 +229,7 @@ public class NetworkCmProxyDataServicePropertyHandler {
         cmHandleData.put(fieldName, newFieldValue);
         dmiRegistryData.put("cm-handles", cmHandleData);
         cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
-                jsonObjectMapper.asJsonString(dmiRegistryData), OffsetDateTime.now());
+                jsonObjectMapper.asJsonString(dmiRegistryData), OffsetDateTime.now(), ContentType.JSON);
         log.debug("Updating {} for cmHandle {} with value : {})", fieldName, cmHandleIdToUpdate, newFieldValue);
     }
 
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory;
 
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
 
 /**
  * It will have all the utility method responsible for handling the composite state.
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory;
 
 public enum DataStoreSyncState {
     SYNCHRONIZED, UNSYNCHRONIZED, NONE_REQUESTED
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
@@ -130,14 +130,12 @@ public interface InventoryPersistence extends NcmpPersistence {
     DataNode getCmHandleDataNodeByAlternateId(String alternateId);
 
     /**
-     * Get data node that matches longest alternate id by removing elements (as defined by the separator string)
-     * from right to left.
+     * Get data nodes for the given batch of alternate ids.
      *
-     * @param alternateId alternate ID
-     * @param separator   a string that separates each element from the next.
-     * @return data node
+     * @param alternateIds alternate IDs
+     * @return data nodes
      */
-    DataNode getCmHandleDataNodeByLongestMatchAlternateId(final String alternateId, final String separator);
+    Collection<DataNode> getCmHandleDataNodesByAlternateIds(Collection<String> alternateIds);
 
     /**
      * Get collection of data nodes of given cm handles.
@@ -154,4 +152,12 @@ public interface InventoryPersistence extends NcmpPersistence {
      * @return Collection of CM handle Ids
      */
     Collection<String> getCmHandleIdsWithGivenModules(Collection<String> moduleNamesForQuery);
+
+    /**
+     * Check database if cm handle id exists if not return false.
+     *
+     * @param cmHandleId cmHandle Id
+     * @return Boolean
+     */
+    boolean isExistingCmHandleId(String cmHandleId);
 }
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory;
 
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
 
@@ -32,21 +32,23 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.ncmp.api.impl.exception.NoAlternateIdParentFoundException;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.impl.utils.CpsValidator;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
-import org.onap.cps.spi.utils.CpsValidator;
+import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Component;
 
@@ -59,7 +61,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     private final CpsModuleService cpsModuleService;
     private final CpsAnchorService cpsAnchorService;
     private final CpsValidator cpsValidator;
-    private final CmHandleQueries cmHandleQueries;
+    private final CmHandleQueryService cmHandleQueryService;
 
     /**
      * initialize an inventory persistence object.
@@ -72,12 +74,13 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
      */
     public InventoryPersistenceImpl(final JsonObjectMapper jsonObjectMapper, final CpsDataService cpsDataService,
                                     final CpsModuleService cpsModuleService, final CpsValidator cpsValidator,
-                                    final CpsAnchorService cpsAnchorService, final CmHandleQueries cmHandleQueries) {
+                                    final CpsAnchorService cpsAnchorService,
+                                    final CmHandleQueryService cmHandleQueryService) {
         super(jsonObjectMapper, cpsDataService, cpsModuleService, cpsValidator);
         this.cpsModuleService = cpsModuleService;
         this.cpsAnchorService = cpsAnchorService;
         this.cpsValidator = cpsValidator;
-        this.cmHandleQueries = cmHandleQueries;
+        this.cmHandleQueryService = cmHandleQueryService;
     }
 
 
@@ -94,7 +97,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
         final String cmHandleJsonData = createStateJsonData(jsonObjectMapper.asJsonString(compositeState));
         cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                getXPathForCmHandleById(cmHandleId), cmHandleJsonData, OffsetDateTime.now());
+                getXPathForCmHandleById(cmHandleId), cmHandleJsonData, OffsetDateTime.now(), ContentType.JSON);
     }
 
     @Override
@@ -104,14 +107,14 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
                 getXPathForCmHandleById(cmHandleId),
                 createStateJsonData(jsonObjectMapper.asJsonString(compositeState))));
         cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                cmHandlesJsonDataMap, OffsetDateTime.now());
+                cmHandlesJsonDataMap, OffsetDateTime.now(), ContentType.JSON);
     }
 
     @Override
     public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
         cpsValidator.validateNameCharacters(cmHandleId);
         final DataNode dataNode = getCmHandleDataNodeByCmHandleId(cmHandleId).iterator().next();
-        return YangDataConverter.convertCmHandleToYangModel(dataNode);
+        return YangDataConverter.toYangModelCmHandle(dataNode);
     }
 
     @Override
@@ -126,7 +129,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
                         dataValidationException.getMessage());
             }
         });
-        return YangDataConverter.convertDataNodesToYangModelCmHandles(getCmHandleDataNodes(validCmHandleIds));
+        return YangDataConverter.toYangModelCmHandles(getCmHandleDataNodes(validCmHandleIds));
     }
 
     @Override
@@ -160,7 +163,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
                 Lists.partition(yangModelCmHandles, CMHANDLE_BATCH_SIZE)) {
             final String cmHandlesJsonData = createCmHandlesJsonData(yangModelCmHandleBatch);
             cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                    NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP);
+                    NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP, ContentType.JSON);
         }
     }
 
@@ -172,7 +175,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     @Override
     public DataNode getCmHandleDataNodeByAlternateId(final String alternateId) {
         final String cpsPathForCmHandleByAlternateId = getCpsPathForCmHandleByAlternateId(alternateId);
-        final Collection<DataNode> dataNodes = cmHandleQueries
+        final Collection<DataNode> dataNodes = cmHandleQueryService
             .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS);
         if (dataNodes.isEmpty()) {
             throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
@@ -182,16 +185,12 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     }
 
     @Override
-    public DataNode getCmHandleDataNodeByLongestMatchAlternateId(final String alternateId, final String separator) {
-        String bestMatch = alternateId;
-        while (StringUtils.isNotEmpty(bestMatch)) {
-            try {
-                return getCmHandleDataNodeByAlternateId(bestMatch);
-            } catch (final DataNodeNotFoundException ignored) {
-                bestMatch = getParentPath(bestMatch, separator);
-            }
+    public Collection<DataNode> getCmHandleDataNodesByAlternateIds(final Collection<String> alternateIds) {
+        if (alternateIds.isEmpty()) {
+            return Collections.emptyList();
         }
-        throw new NoAlternateIdParentFoundException(alternateId);
+        final String cpsPathForCmHandlesByAlternateIds = getCpsPathForCmHandlesByAlternateIds(alternateIds);
+        return cmHandleQueryService.queryNcmpRegistryByCpsPath(cpsPathForCmHandlesByAlternateIds, OMIT_DESCENDANTS);
     }
 
     @Override
@@ -206,6 +205,15 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
         return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
     }
 
+    @Override
+    public boolean isExistingCmHandleId(final String cmHandleId) {
+        try {
+            return  !getCmHandleDataNodeByCmHandleId(cmHandleId).isEmpty();
+        } catch (final DataNodeNotFoundException exception) {
+            return false;
+        }
+    }
+
     private static String getXPathForCmHandleById(final String cmHandleId) {
         return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']";
     }
@@ -214,6 +222,11 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
         return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='" + alternateId + "']";
     }
 
+    private static String getCpsPathForCmHandlesByAlternateIds(final Collection<String> alternateIds) {
+        return alternateIds.stream().collect(Collectors.joining("' or @alternate-id='",
+                NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='", "']"));
+    }
+
     private static String createStateJsonData(final String state) {
         return "{\"state\":" + state + "}";
     }
@@ -221,9 +234,4 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     private String createCmHandlesJsonData(final List<YangModelCmHandle> yangModelCmHandles) {
         return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}";
     }
-
-    private static String getParentPath(final String path, final String separator) {
-        final int lastSeparatorIndex = path.lastIndexOf(separator);
-        return lastSeparatorIndex < 0 ? "" : path.substring(0, lastSeparatorIndex);
-    }
 }
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory;
 
 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
@@ -29,11 +29,10 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.DataNode;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Component;
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api;
+package org.onap.cps.ncmp.impl.inventory;
 
 import java.util.Collection;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 
-public interface NetworkCmProxyCmHandleQueryService {
+public interface ParameterizedCmHandleQueryService {
     /**
      * Query and return cm handle ids that match the given query parameters.
      * Supported query types:
@@ -51,21 +51,14 @@ public interface NetworkCmProxyCmHandleQueryService {
     Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
 
     /**
-     * Query and return cm handle objects that match the given query parameters.
+     * Query and return yang model cm handle objects that match the given query parameters.
      * Supported query types:
      *      public properties
      *      modules
      *      cps-path
      *
      * @param cmHandleQueryServiceParameters the cm handle query parameters
-     * @return collection of cm handles
+     * @return collection of yang model cm handles
      */
-    Collection<NcmpServiceCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
-
-    /**
-     * Query and return all cm handle objects.
-     *
-     * @return collection of cm handles
-     */
-    Collection<NcmpServiceCmHandle> getAllCmHandles();
+    Collection<YangModelCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl;
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
-import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.HAS_ALL_MODULES;
-import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.HAS_ALL_PROPERTIES;
-import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.WITH_CPS_PATH;
-import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.WITH_TRUST_LEVEL;
-import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCpsPathConditionProperties;
-import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateModuleNameConditionProperties;
-import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle;
+package org.onap.cps.ncmp.impl.inventory;
+
+import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateCpsPathConditionProperties;
+import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateModuleNameConditionProperties;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.HAS_ALL_MODULES;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.HAS_ALL_PROPERTIES;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.WITH_CPS_PATH;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.WITH_TRUST_LEVEL;
 import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
 
@@ -40,67 +39,58 @@ import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.cpspath.parser.PathParsingException;
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.inventory.enums.PropertyType;
-import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions;
+import org.onap.cps.ncmp.impl.inventory.models.PropertyType;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.ConditionProperties;
 import org.onap.cps.spi.model.DataNode;
 import org.springframework.stereotype.Service;
 
 @Service
-@Slf4j
 @RequiredArgsConstructor
-public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmHandleQueryService {
+public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHandleQueryService {
 
     private static final Collection<String> NO_QUERY_TO_EXECUTE = null;
-    private final CmHandleQueries cmHandleQueries;
+    private final CmHandleQueryService cmHandleQueryService;
     private final InventoryPersistence inventoryPersistence;
 
     @Override
     public Collection<String> queryCmHandleIds(
             final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
         return executeQueries(cmHandleQueryServiceParameters,
-            this::executeCpsPathQuery,
-            this::queryCmHandlesByPublicProperties,
-            this::executeModuleNameQuery,
+                this::executeCpsPathQuery,
+                this::queryCmHandlesByPublicProperties,
+                this::executeModuleNameQuery,
                 this::queryCmHandlesByTrustLevel);
     }
 
     @Override
     public Collection<String> queryCmHandleIdsForInventory(
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
         return executeQueries(cmHandleQueryServiceParameters,
-            this::queryCmHandlesByPublicProperties,
-            this::queryCmHandlesByPrivateProperties,
-            this::queryCmHandlesByDmiPlugin);
+                this::executeCpsPathQuery,
+                this::queryCmHandlesByPublicProperties,
+                this::queryCmHandlesByPrivateProperties,
+                this::queryCmHandlesByDmiPlugin);
     }
 
     @Override
-    public Collection<NcmpServiceCmHandle> queryCmHandles(
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-
+    public Collection<YangModelCmHandle> queryCmHandles(
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
         if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
             return getAllCmHandles();
         }
-
         final Collection<String> cmHandleIds = queryCmHandleIds(cmHandleQueryServiceParameters);
-
-        return getNcmpServiceCmHandles(cmHandleIds);
+        return inventoryPersistence.getYangModelCmHandles(cmHandleIds);
     }
 
-    @Override
-    public Collection<NcmpServiceCmHandle> getAllCmHandles() {
+    private Collection<YangModelCmHandle> getAllCmHandles() {
         final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT).iterator().next();
-        return dataNode.getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
+        return dataNode.getChildDataNodes().stream().map(YangDataConverter::toYangModelCmHandle).toList();
     }
 
     private Collection<String> queryCmHandlesByDmiPlugin(
@@ -115,7 +105,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH
         final String dmiPluginIdentifierValue = dmiPropertyQueryPairs
             .get(PropertyType.DMI_PLUGIN.getYangContainerName());
 
-        return cmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue);
+        return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue);
     }
 
     private Collection<String> queryCmHandlesByPrivateProperties(
@@ -128,7 +118,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH
         if (privatePropertyQueryPairs.isEmpty()) {
             return NO_QUERY_TO_EXECUTE;
         }
-        return cmHandleQueries.queryCmHandleAdditionalProperties(privatePropertyQueryPairs);
+        return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs);
     }
 
     private Collection<String> queryCmHandlesByPublicProperties(
@@ -141,7 +131,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH
         if (publicPropertyQueryPairs.isEmpty()) {
             return NO_QUERY_TO_EXECUTE;
         }
-        return cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs);
+        return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs);
     }
 
     private Collection<String> queryCmHandlesByTrustLevel(final CmHandleQueryServiceParameters
@@ -154,7 +144,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH
         if (trustLevelPropertyQueryPairs.isEmpty()) {
             return NO_QUERY_TO_EXECUTE;
         }
-        return cmHandleQueries.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs);
+        return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs);
     }
 
     private Collection<String> executeModuleNameQuery(
@@ -180,7 +170,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH
         }
         try {
             cpsPathQueryResult = collectCmHandleIdsFromDataNodes(
-                cmHandleQueries.queryCmHandleAncestorsByCpsPath(
+                cmHandleQueryService.queryCmHandleAncestorsByCpsPath(
                         cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS));
         } catch (final PathParsingException pathParsingException) {
             throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(),
@@ -228,22 +218,6 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH
         return collectCmHandleIdsFromDataNodes(dataNode.getChildDataNodes());
     }
 
-    private Collection<NcmpServiceCmHandle> getNcmpServiceCmHandles(final Collection<String> cmHandleIds) {
-        final Collection<YangModelCmHandle> yangModelcmHandles
-            = inventoryPersistence.getYangModelCmHandles(cmHandleIds);
-
-        final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = new ArrayList<>(yangModelcmHandles.size());
-
-        yangModelcmHandles.forEach(yangModelcmHandle ->
-            ncmpServiceCmHandles.add(YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle(yangModelcmHandle))
-        );
-        return ncmpServiceCmHandles;
-    }
-
-    private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) {
-        return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter.convertCmHandleToYangModel(dataNode));
-    }
-
     private Collection<String> executeQueries(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters,
                                               final Function<CmHandleQueryServiceParameters, Collection<String>>...
                                                   queryFunctions) {
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory;
+package org.onap.cps.ncmp.impl.inventory.models;
 
 public enum CmHandleState {
     ADVISED, READY, LOCKED, DELETING, DELETED
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils;
+package org.onap.cps.ncmp.impl.inventory.models;
 
 import java.util.Arrays;
 import java.util.List;
@@ -32,7 +32,8 @@ public enum InventoryQueryConditions {
 
     HAS_ALL_PROPERTIES("hasAllProperties"),
     HAS_ALL_ADDITIONAL_PROPERTIES("hasAllAdditionalProperties"),
-    CM_HANDLE_WITH_DMI_PLUGIN("cmHandleWithDmiPlugin");
+    CM_HANDLE_WITH_DMI_PLUGIN("cmHandleWithDmiPlugin"),
+    WITH_CPS_PATH("cmHandleWithCpsPath");
 
     public static final List<String> ALL_CONDITION_NAMES = Arrays.stream(InventoryQueryConditions.values())
         .map(InventoryQueryConditions::getName).collect(Collectors.toList());
@@ -18,8 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-
-package org.onap.cps.ncmp.api.impl.yangmodels;
+package org.onap.cps.ncmp.impl.inventory.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -34,9 +33,9 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 import org.apache.commons.lang3.StringUtils;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.impl.models.RequiredDmiService;
 
 /**
  * Cm Handle which follows the Yang resource dmi registry model when persisting data to DMI or the DB.
@@ -126,10 +125,9 @@ public class YangModelCmHandle {
         yangModelCmHandle.setDmiServiceName(dmiServiceName);
         yangModelCmHandle.setDmiDataServiceName(dmiDataServiceName);
         yangModelCmHandle.setDmiModelServiceName(dmiModelServiceName);
-        yangModelCmHandle.setModuleSetTag(moduleSetTag == null ? StringUtils.EMPTY : moduleSetTag);
-        yangModelCmHandle.setAlternateId(alternateId == null ? StringUtils.EMPTY : alternateId);
-        yangModelCmHandle.setDataProducerIdentifier(
-            dataProducerIdentifier == null ? StringUtils.EMPTY : dataProducerIdentifier);
+        yangModelCmHandle.setModuleSetTag(StringUtils.trimToEmpty(moduleSetTag));
+        yangModelCmHandle.setAlternateId(StringUtils.trimToEmpty(alternateId));
+        yangModelCmHandle.setDataProducerIdentifier(StringUtils.trimToEmpty(dataProducerIdentifier));
         yangModelCmHandle.setDmiProperties(asYangModelCmHandleProperties(ncmpServiceCmHandle.getDmiProperties()));
         yangModelCmHandle.setPublicProperties(asYangModelCmHandleProperties(
                 ncmpServiceCmHandle.getPublicProperties()));
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -18,9 +18,9 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync;
+package org.onap.cps.ncmp.impl.inventory.sync;
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
 
 import com.hazelcast.map.IMap;
 import java.time.OffsetDateTime;
@@ -29,10 +29,9 @@ import java.util.function.Consumer;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsDataService;
-import org.onap.cps.ncmp.api.impl.config.embeddedcache.SynchronizationCacheConfig;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
@@ -61,21 +60,22 @@ public class DataSyncWatchdog {
         moduleOperationsUtils.getUnsynchronizedReadyCmHandles().forEach(unSynchronizedReadyCmHandle -> {
             final String cmHandleId = unSynchronizedReadyCmHandle.getId();
             if (hasPushedIntoSemaphoreMap(cmHandleId)) {
-                log.debug("Executing data sync on {}", cmHandleId);
+                log.info("Executing data sync on {}", cmHandleId);
                 final CompositeState compositeState = inventoryPersistence
                         .getCmHandleState(cmHandleId);
                 final String resourceData = moduleOperationsUtils.getResourceData(cmHandleId);
                 if (resourceData == null) {
-                    log.debug("Error retrieving resource data for Cm-Handle: {}", cmHandleId);
+                    log.error("Error retrieving resource data for Cm-Handle: {}", cmHandleId);
                 } else {
                     cpsDataService.saveData(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId,
                             resourceData, OffsetDateTime.now());
                     setSyncStateToSynchronized().accept(compositeState);
                     inventoryPersistence.saveCmHandleState(cmHandleId, compositeState);
                     updateDataSyncSemaphoreMap(cmHandleId);
+                    log.info("Data sync finished for {}", cmHandleId);
                 }
             } else {
-                log.debug("{} already processed by another instance", cmHandleId);
+                log.info("{} already processed by another instance", cmHandleId);
             }
         });
         log.debug("No Cm-Handles currently found in READY State and Operational Sync State is UNSYNCHRONIZED");
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.impl.inventory.sync;
 
-import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL;
+import static org.onap.cps.ncmp.api.data.models.OperationType.READ;
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL;
 
 import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.YangResource;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.inventory.models.YangResource;
+import org.onap.cps.ncmp.impl.dmi.DmiProperties;
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.models.DmiRequestBody;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
 import org.onap.cps.spi.model.ModuleReference;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
 
 /**
  * Operations class for DMI Model.
  */
-@Component
-public class DmiModelOperations extends DmiOperations {
+@RequiredArgsConstructor
+@Service
+public class DmiModelOperations {
 
-    /**
-     * Constructor for {@code DmiOperations}. This method also manipulates url properties.
-     *
-     * @param dmiRestClient {@code DmiRestClient}
-     */
-    public DmiModelOperations(final InventoryPersistence inventoryPersistence,
-                              final JsonObjectMapper jsonObjectMapper,
-                              final NcmpConfiguration.DmiProperties dmiProperties,
-                              final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
-        super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder);
-    }
+    private final JsonObjectMapper jsonObjectMapper;
+    private final DmiProperties dmiProperties;
+    private final DmiRestClient dmiRestClient;
 
     /**
      * Retrieves module references.
@@ -113,9 +107,13 @@ public class DmiModelOperations extends DmiOperations {
                                                                   final String jsonRequestBody,
                                                                   final String cmHandle,
                                                                   final String resourceName) {
-        final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody,
-                OperationType.READ, null);
+        final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
+                .fixedPathSegment("ch")
+                .variablePathSegment("cmHandleId", cmHandle)
+                .fixedPathSegment(resourceName)
+                .createUrlTemplateParameters(dmiServiceName, dmiProperties.getDmiBasePath());
+        return dmiRestClient.synchronousPostOperationWithJsonData(MODEL, urlTemplateParameters, jsonRequestBody, READ,
+                null);
     }
 
     private static String getRequestBodyToFetchYangResources(final Collection<ModuleReference> newModuleReferences,
@@ -126,8 +124,7 @@ public class DmiModelOperations extends DmiOperations {
         data.add("modules", moduleReferencesAsJson);
         final JsonObject jsonRequestObject = new JsonObject();
         if (!moduleSetTag.isEmpty()) {
-            final JsonElement moduleSetTagAsJson = JsonParser.parseString(moduleSetTag);
-            jsonRequestObject.add("moduleSetTag", moduleSetTagAsJson);
+            jsonRequestObject.addProperty("moduleSetTag", moduleSetTag);
         }
         jsonRequestObject.add("data", data);
         jsonRequestObject.add("cmHandleProperties", toJsonObject(dmiProperties));
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync;
-
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL;
+package org.onap.cps.ncmp.impl.inventory.sync;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import java.time.Duration;
-import java.time.OffsetDateTime;
-import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -38,14 +34,14 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState;
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory;
-import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.impl.data.DmiDataOperations;
+import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService;
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.utils.JsonObjectMapper;
@@ -57,14 +53,13 @@ import org.springframework.stereotype.Service;
 @RequiredArgsConstructor
 public class ModuleOperationsUtils {
 
-    private final CmHandleQueries cmHandleQueries;
+    private final CmHandleQueryService cmHandleQueryService;
     private final DmiDataOperations dmiDataOperations;
     private final JsonObjectMapper jsonObjectMapper;
     private static final String RETRY_ATTEMPT_KEY = "attempt";
     private static final String MODULE_SET_TAG_KEY = "moduleSetTag";
     public static final String MODULE_SET_TAG_MESSAGE_FORMAT = "Upgrade to ModuleSetTag: %s";
-    private static final String LOCK_REASON_DETAILS_MSG_FORMAT =
-            MODULE_SET_TAG_MESSAGE_FORMAT + " Attempt #%d failed: %s";
+    private static final String LOCK_REASON_DETAILS_MSG_FORMAT = " Attempt #%d failed: %s";
     private static final Pattern retryAttemptPattern = Pattern.compile("Attempt #(\\d+) failed:.+");
     private static final Pattern moduleSetTagPattern = Pattern.compile("Upgrade to ModuleSetTag: (\\S+)");
     private static final String CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE = """
@@ -77,8 +72,9 @@ public class ModuleOperationsUtils {
      *
      * @return cm handles (data nodes) in ADVISED state (empty list if none found)
      */
-    public List<DataNode> getAdvisedCmHandles() {
-        final List<DataNode> advisedCmHandlesAsDataNodes = cmHandleQueries.queryCmHandlesByState(CmHandleState.ADVISED);
+    public Collection<DataNode> getAdvisedCmHandles() {
+        final Collection<DataNode> advisedCmHandlesAsDataNodes =
+            cmHandleQueryService.queryCmHandlesByState(CmHandleState.ADVISED);
         log.debug("Total number of fetched advised cm handle(s) is (are) {}", advisedCmHandlesAsDataNodes.size());
         return advisedCmHandlesAsDataNodes;
     }
@@ -91,13 +87,13 @@ public class ModuleOperationsUtils {
      *         return empty list if not found
      */
     public List<YangModelCmHandle> getUnsynchronizedReadyCmHandles() {
-        final List<DataNode> unsynchronizedCmHandles = cmHandleQueries
+        final Collection<DataNode> unsynchronizedCmHandles = cmHandleQueryService
                 .queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED);
 
         final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>();
         for (final DataNode unsynchronizedCmHandle : unsynchronizedCmHandles) {
             final String cmHandleId = unsynchronizedCmHandle.getLeaves().get("id").toString();
-            if (cmHandleQueries.cmHandleHasState(cmHandleId, CmHandleState.READY)) {
+            if (cmHandleQueryService.cmHandleHasState(cmHandleId, CmHandleState.READY)) {
                 yangModelCmHandles.addAll(convertCmHandlesDataNodesToYangModelCmHandles(
                                 Collections.singletonList(unsynchronizedCmHandle)));
             }
@@ -111,31 +107,33 @@ public class ModuleOperationsUtils {
      *
      * @return a random LOCKED yang model cm handle, return null if not found
      */
-    public List<YangModelCmHandle> getCmHandlesThatFailedModelSyncOrUpgrade() {
-        final List<DataNode> lockedCmHandlesAsDataNodeList
-                = cmHandleQueries.queryCmHandleAncestorsByCpsPath(CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE,
+    public Collection<YangModelCmHandle> getCmHandlesThatFailedModelSyncOrUpgrade() {
+        final Collection<DataNode> lockedCmHandlesAsDataNodeList
+                = cmHandleQueryService.queryCmHandleAncestorsByCpsPath(CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE,
                 FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
         return convertCmHandlesDataNodesToYangModelCmHandles(lockedCmHandlesAsDataNodeList);
     }
 
     /**
-     * Update Composite State attempts counter and set new lock reason and details.
+     * Updates the lock reason message and attempt counter for the provided CompositeState.
+     * This method increments the attempt counter and updates the lock reason message,
+     * including the module set tag if available.
      *
-     * @param lockReasonCategory lock reason category
-     * @param errorMessage       error message
+     * @param compositeState     the composite state of the CM handle
+     * @param lockReasonCategory the lock reason category for the CM handle
+     * @param errorMessage       the error message to include in the lock reason message
      */
-    public void updateLockReasonDetailsAndAttempts(final CompositeState compositeState,
-                                                   final LockReasonCategory lockReasonCategory,
-                                                   final String errorMessage) {
-        int attempt = 1;
-        final Map<String, String> compositeStateDetails
-                = getLockedCompositeStateDetails(compositeState.getLockReason());
-        if (!compositeStateDetails.isEmpty() && compositeStateDetails.containsKey(RETRY_ATTEMPT_KEY)) {
-            attempt = 1 + Integer.parseInt(compositeStateDetails.get(RETRY_ATTEMPT_KEY));
-        }
-        final String moduleSetTag = compositeStateDetails.getOrDefault(MODULE_SET_TAG_KEY, "");
+    public void updateLockReasonWithAttempts(final CompositeState compositeState,
+                                             final LockReasonCategory lockReasonCategory,
+                                             final String errorMessage) {
+        final Map<String, String> lockedStateDetails = getLockedCompositeStateDetails(compositeState.getLockReason());
+        final int nextAttemptCount = calculateNextAttemptCount(lockedStateDetails);
+        final String moduleSetTag = lockedStateDetails.getOrDefault(MODULE_SET_TAG_KEY, "");
+
+        final String lockReasonMessage = buildLockReasonDetails(moduleSetTag, nextAttemptCount, errorMessage);
+
         compositeState.setLockReason(CompositeState.LockReason.builder()
-                .details(String.format(LOCK_REASON_DETAILS_MSG_FORMAT, moduleSetTag, attempt, errorMessage))
+                .details(lockReasonMessage)
                 .lockReasonCategory(lockReasonCategory)
                 .build());
     }
@@ -166,48 +164,15 @@ public class ModuleOperationsUtils {
         return Collections.emptyMap();
     }
 
-
     /**
-     * Check if a module sync retry is needed.
-     *
-     * @param compositeState the composite state currently in the locked state
-     * @return if the retry mechanism should be attempted
-     */
-    public boolean needsModuleSyncRetryOrUpgrade(final CompositeState compositeState) {
-        final OffsetDateTime time = OffsetDateTime.parse(compositeState.getLastUpdateTime(),
-                DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
-        final CompositeState.LockReason lockReason = compositeState.getLockReason();
-
-        final boolean moduleUpgrade = LockReasonCategory.MODULE_UPGRADE == lockReason.getLockReasonCategory();
-        if (moduleUpgrade) {
-            log.info("Locked for module upgrade");
-            return true;
-        }
-
-        final boolean failedDuringModuleSync = LockReasonCategory.MODULE_SYNC_FAILED
-                == lockReason.getLockReasonCategory();
-        final boolean failedDuringModuleUpgrade = LockReasonCategory.MODULE_UPGRADE_FAILED
-                == lockReason.getLockReasonCategory();
-
-        if (failedDuringModuleSync || failedDuringModuleUpgrade) {
-            log.info("Locked for module {} (last attempt failed).", failedDuringModuleSync ? "sync" : "upgrade");
-            return isRetryDue(lockReason, time);
-        }
-        log.info("Locked for other reason");
-        return false;
-    }
-
-    /**
-     * Get the Resourece Data from Node through DMI Passthrough service.
+     * Get the Resource Data from Node through DMI Passthrough service.
      *
      * @param cmHandleId cm handle id
      * @return optional string containing the resource data
      */
     public String getResourceData(final String cmHandleId) {
-        final ResponseEntity<Object> resourceDataResponseEntity = dmiDataOperations.getResourceDataFromDmi(
-                PASSTHROUGH_OPERATIONAL.getDatastoreName(),
-                cmHandleId,
-                UUID.randomUUID().toString());
+        final ResponseEntity<Object> resourceDataResponseEntity = dmiDataOperations.getAllResourceDataFromDmi(
+                cmHandleId, UUID.randomUUID().toString());
         if (resourceDataResponseEntity.getStatusCode().is2xxSuccessful()) {
             return getFirstResource(resourceDataResponseEntity.getBody());
         }
@@ -239,27 +204,23 @@ public class ModuleOperationsUtils {
         return jsonObjectMapper.asJsonString(Map.of(firstElement.getKey(), firstElement.getValue()));
     }
 
-    private List<YangModelCmHandle> convertCmHandlesDataNodesToYangModelCmHandles(
-            final List<DataNode> cmHandlesAsDataNodeList) {
-        return cmHandlesAsDataNodeList.stream().map(YangDataConverter::convertCmHandleToYangModel).toList();
+    private Collection<YangModelCmHandle> convertCmHandlesDataNodesToYangModelCmHandles(
+            final Collection<DataNode> cmHandlesAsDataNodeList) {
+        return cmHandlesAsDataNodeList.stream().map(YangDataConverter::toYangModelCmHandle).toList();
     }
 
-    private boolean isRetryDue(final CompositeState.LockReason compositeStateLockReason, final OffsetDateTime time) {
-        final int timeInMinutesUntilNextAttempt;
-        final Map<String, String> compositeStateDetails = getLockedCompositeStateDetails(compositeStateLockReason);
-        if (compositeStateDetails.isEmpty()) {
-            timeInMinutesUntilNextAttempt = 1;
-            log.info("First Attempt: no current attempts found.");
-        } else {
-            timeInMinutesUntilNextAttempt = (int) Math.pow(2, Integer.parseInt(compositeStateDetails
-                    .get(RETRY_ATTEMPT_KEY)));
-        }
-        final int timeSinceLastAttempt = (int) Duration.between(time, OffsetDateTime.now()).toMinutes();
-        if (timeInMinutesUntilNextAttempt >= timeSinceLastAttempt) {
-            log.info("Time until next attempt is {} minutes: ", timeInMinutesUntilNextAttempt - timeSinceLastAttempt);
-            return false;
+    private int calculateNextAttemptCount(final Map<String, String> compositeStateDetails) {
+        return compositeStateDetails.containsKey(RETRY_ATTEMPT_KEY)
+                ? 1 + Integer.parseInt(compositeStateDetails.get(RETRY_ATTEMPT_KEY))
+                : 1;
+    }
+
+    private String buildLockReasonDetails(final String moduleSetTag, final int attempt, final String errorMessage) {
+        if (moduleSetTag.isEmpty()) {
+            return String.format(LOCK_REASON_DETAILS_MSG_FORMAT, attempt, errorMessage);
         }
-        log.info("Retry due now");
-        return true;
+        return String.format(MODULE_SET_TAG_MESSAGE_FORMAT + " " + LOCK_REASON_DETAILS_MSG_FORMAT,
+                moduleSetTag, attempt, errorMessage);
     }
+
 }
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync;
+package org.onap.cps.ncmp.impl.inventory.sync;
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
 
 import java.time.OffsetDateTime;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import lombok.AllArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.onap.cps.spi.CascadeDeleteAllowed;
-import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
-import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleReference;
+import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Service;
 
@@ -57,7 +52,6 @@ public class ModuleSyncService {
 
     private final DmiModelOperations dmiModelOperations;
     private final CpsModuleService cpsModuleService;
-    private final CmHandleQueries cmHandleQueries;
     private final CpsDataService cpsDataService;
     private final CpsAnchorService cpsAnchorService;
     private final JsonObjectMapper jsonObjectMapper;
@@ -112,40 +106,32 @@ public class ModuleSyncService {
     }
 
     private ModuleDelta getModuleDelta(final YangModelCmHandle yangModelCmHandle, final String targetModuleSetTag) {
-        final Collection<ModuleReference> allModuleReferences;
         final Map<String, String> newYangResources;
-
-        final YangModelCmHandle cmHandleWithSameModuleSetTag = getAnyReadyCmHandleByModuleSetTag(targetModuleSetTag);
-        if (cmHandleWithSameModuleSetTag == null) {
+        Collection<ModuleReference> allModuleReferences = getModuleReferencesByModuleSetTag(targetModuleSetTag);
+        if (allModuleReferences.isEmpty()) {
             allModuleReferences = dmiModelOperations.getModuleReferences(yangModelCmHandle);
             newYangResources = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle,
                     cpsModuleService.identifyNewModuleReferences(allModuleReferences));
         } else {
             log.info("Found other cm handle having same module set tag: {}", targetModuleSetTag);
-            allModuleReferences = cpsModuleService.getYangResourcesModuleReferences(
-                    NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleWithSameModuleSetTag.getId());
             newYangResources = NO_NEW_MODULES;
         }
         return new ModuleDelta(allModuleReferences, newYangResources);
     }
 
-    private YangModelCmHandle getAnyReadyCmHandleByModuleSetTag(final String moduleSetTag) {
-        if (StringUtils.isBlank(moduleSetTag)) {
-            return null;
+    private Collection<ModuleReference> getModuleReferencesByModuleSetTag(final String moduleSetTag) {
+        if (moduleSetTag == null || moduleSetTag.trim().isEmpty()) {
+            return Collections.emptyList();
         }
-        final String escapedModuleSetTag = moduleSetTag.replace("'", "''");
-        final List<DataNode> dataNodes = cmHandleQueries.queryNcmpRegistryByCpsPath(
-                NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@module-set-tag='" + escapedModuleSetTag + "']",
-                FetchDescendantsOption.DIRECT_CHILDREN_ONLY);
-        return dataNodes.stream().map(YangDataConverter::convertCmHandleToYangModel)
-                .filter(cmHandle -> cmHandle.getCompositeState().getCmHandleState() == CmHandleState.READY)
-                .findFirst().orElse(null);
+        return cpsModuleService.getModuleReferencesByAttribute(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                Map.of("module-set-tag", moduleSetTag), Map.of("cm-handle-state", CmHandleState.READY.name()));
     }
 
     private void setCmHandleModuleSetTag(final YangModelCmHandle yangModelCmHandle, final String newModuleSetTag) {
         final String jsonForUpdate = jsonObjectMapper.asJsonString(Map.of(
                 "cm-handles", Map.of("id", yangModelCmHandle.getId(), "module-set-tag", newModuleSetTag)));
         cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
-                jsonForUpdate, OffsetDateTime.now());
+                jsonForUpdate, OffsetDateTime.now(), ContentType.JSON);
     }
+
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync;
+package org.onap.cps.ncmp.impl.inventory.sync;
 
 import com.hazelcast.map.IMap;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicInteger;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.onap.cps.spi.model.DataNode;
 import org.springframework.stereotype.Component;
 
@@ -64,8 +63,7 @@ public class ModuleSyncTasks {
                     = new HashMap<>(cmHandlesAsDataNodes.size());
             for (final DataNode cmHandleAsDataNode : cmHandlesAsDataNodes) {
                 final String cmHandleId = String.valueOf(cmHandleAsDataNode.getLeaves().get("id"));
-                final YangModelCmHandle yangModelCmHandle =
-                        YangDataConverter.convertCmHandleToYangModel(cmHandleAsDataNode);
+                final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(cmHandleAsDataNode);
                 final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId);
                 final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState);
                 try {
@@ -81,7 +79,7 @@ public class ModuleSyncTasks {
                     log.warn("Processing of {} module failed due to reason {}.", cmHandleId, e.getMessage());
                     final LockReasonCategory lockReasonCategory = inUpgrade ? LockReasonCategory.MODULE_UPGRADE_FAILED
                             : LockReasonCategory.MODULE_SYNC_FAILED;
-                    moduleOperationsUtils.updateLockReasonDetailsAndAttempts(compositeState,
+                    moduleOperationsUtils.updateLockReasonWithAttempts(compositeState,
                             lockReasonCategory, e.getMessage());
                     setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason());
                     cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.LOCKED);
@@ -97,23 +95,23 @@ public class ModuleSyncTasks {
     }
 
     /**
-     * Reset state to "ADVISED" for any previously failed cm handles.
+     * Resets the state of failed CM handles and updates their status to ADVISED for retry.
+
+     * This method processes a collection of failed CM handles, logs their lock reason, and resets their state
+     * to ADVISED. Once reset, it updates the CM handle states in a batch to allow for re-attempt by the module-sync
+     * watchdog.
      *
-     * @param failedCmHandles previously failed (locked) cm handles
+     * @param failedCmHandles a collection of CM handles that have failed and need their state reset
      */
-    public void resetFailedCmHandles(final List<YangModelCmHandle> failedCmHandles) {
+    public void resetFailedCmHandles(final Collection<YangModelCmHandle> failedCmHandles) {
         final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(failedCmHandles.size());
         for (final YangModelCmHandle failedCmHandle : failedCmHandles) {
             final CompositeState compositeState = failedCmHandle.getCompositeState();
-            final boolean isReadyForRetry = moduleOperationsUtils.needsModuleSyncRetryOrUpgrade(compositeState);
-            log.info("Retry for cmHandleId : {} is {}", failedCmHandle.getId(), isReadyForRetry);
-            if (isReadyForRetry) {
-                final String resetCmHandleId = failedCmHandle.getId();
-                log.debug("Reset cm handle {} state to ADVISED to be re-attempted by module-sync watchdog",
-                        resetCmHandleId);
-                cmHandleStatePerCmHandle.put(failedCmHandle, CmHandleState.ADVISED);
-                removeResetCmHandleFromModuleSyncMap(resetCmHandleId);
-            }
+            final String resetCmHandleId = failedCmHandle.getId();
+            log.debug("Resetting CM handle {} state to ADVISED for retry by the module-sync watchdog. Lock reason: {}",
+                    failedCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name());
+            cmHandleStatePerCmHandle.put(failedCmHandle, CmHandleState.ADVISED);
+            removeResetCmHandleFromModuleSyncMap(resetCmHandleId);
         }
         lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
     }
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync;
+package org.onap.cps.ncmp.impl.inventory.sync;
 
 import com.hazelcast.map.IMap;
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.config.embeddedcache.SynchronizationCacheConfig;
-import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.onap.cps.spi.model.DataNode;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -85,10 +82,10 @@ public class ModuleSyncWatchdog {
     /**
      * Find any failed (locked) cm handles and change state back to 'ADVISED'.
      */
-    @Scheduled(fixedDelayString = "${ncmp.timers.locked-modules-sync.sleep-time-ms:300000}")
+    @Scheduled(fixedDelayString = "${ncmp.timers.locked-modules-sync.sleep-time-ms:15000}")
     public void resetPreviouslyFailedCmHandles() {
         log.info("Processing module sync retry-watchdog waking up.");
-        final List<YangModelCmHandle> failedCmHandles
+        final Collection<YangModelCmHandle> failedCmHandles
                 = moduleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade();
         log.info("Retrying {} cmHandles", failedCmHandles.size());
         moduleSyncTasks.resetFailedCmHandles(failedCmHandles);
@@ -105,7 +102,7 @@ public class ModuleSyncWatchdog {
 
     private void populateWorkQueueIfNeeded() {
         if (moduleSyncWorkQueue.isEmpty()) {
-            final List<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles();
+            final Collection<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles();
             log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size());
             for (final DataNode advisedCmHandle : advisedCmHandles) {
                 if (!moduleSyncWorkQueue.offer(advisedCmHandle)) {
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync.config;
+package org.onap.cps.ncmp.impl.inventory.sync;
 
 import java.util.concurrent.ThreadPoolExecutor;
 import org.springframework.context.annotation.Bean;
@@ -53,4 +53,4 @@ public class WatchdogSchedulingConfigurer implements SchedulingConfigurer {
         taskScheduler.initialize();
         return taskScheduler;
     }
-}
\ No newline at end of file
+}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm;
+package org.onap.cps.ncmp.impl.inventory.sync.lcm;
 
 import java.util.Collection;
 import java.util.Map;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 
 /**
  * The implementation of it should handle the persisting of composite state and delegate the request to publish the
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm;
+package org.onap.cps.ncmp.impl.inventory.sync.lcm;
 
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent;
 import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
@@ -74,6 +74,6 @@ public class LcmEventsCmHandleStateHandlerAsyncHelper {
     }
 
     private static NcmpServiceCmHandle toNcmpServiceCmHandle(final YangModelCmHandle yangModelCmHandle) {
-        return YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle(yangModelCmHandle);
+        return YangDataConverter.toNcmpServiceCmHandle(yangModelCmHandle);
     }
 }
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm;
+package org.onap.cps.ncmp.impl.inventory.sync.lcm;
 
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED;
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED;
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.LOCKED;
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.LOCKED;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY;
 
 import io.micrometer.core.annotation.Timed;
 import java.util.ArrayList;
@@ -37,13 +37,13 @@ import lombok.NoArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateUtils;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.impl.inventory.CompositeStateUtils;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -194,7 +194,7 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState
     }
 
     private NcmpServiceCmHandle toNcmpServiceCmHandle(final YangModelCmHandle yangModelCmHandle) {
-        return YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle(yangModelCmHandle);
+        return YangDataConverter.toNcmpServiceCmHandle(yangModelCmHandle);
     }
 
     @Getter
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm;
+package org.onap.cps.ncmp.impl.inventory.sync.lcm;
 
 import java.util.UUID;
 import lombok.Getter;
@@ -26,12 +26,12 @@ import lombok.NoArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.utils.EventDateTimeFormatter;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.events.lcm.v1.Event;
 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent;
 import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader;
 import org.onap.cps.ncmp.events.lcm.v1.Values;
+import org.onap.cps.ncmp.impl.utils.EventDateTimeFormatter;
 import org.springframework.stereotype.Component;
 
 
@@ -128,4 +128,4 @@ public class LcmEventsCreator {
         private Values newValues;
     }
 
-}
\ No newline at end of file
+}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm;
+package org.onap.cps.ncmp.impl.inventory.sync.lcm;
 
-import static org.onap.cps.ncmp.api.impl.events.lcm.LcmEventType.CREATE;
-import static org.onap.cps.ncmp.api.impl.events.lcm.LcmEventType.DELETE;
-import static org.onap.cps.ncmp.api.impl.events.lcm.LcmEventType.UPDATE;
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED;
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED;
+import static org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventType.CREATE;
+import static org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventType.DELETE;
+import static org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventType.UPDATE;
 
 import com.google.common.collect.MapDifference;
 import com.google.common.collect.Maps;
@@ -33,7 +33,7 @@ import java.util.Map;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.events.lcm.v1.Values;
 
 /**
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm;
+package org.onap.cps.ncmp.impl.inventory.sync.lcm;
 
-import io.micrometer.core.annotation.Timed;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Timer;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.events.EventsPublisher;
 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent;
 import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader;
+import org.onap.cps.ncmp.events.lcm.v1.Values;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.kafka.KafkaException;
@@ -41,8 +46,12 @@ import org.springframework.stereotype.Service;
 @RequiredArgsConstructor
 public class LcmEventsService {
 
+    private static final Tag TAG_METHOD = Tag.of("method", "publishLcmEvent");
+    private static final Tag TAG_CLASS = Tag.of("class", LcmEventsService.class.getName());
+    private static final String UNAVAILABLE_CM_HANDLE_STATE = "N/A";
     private final EventsPublisher<LcmEvent> eventsPublisher;
     private final JsonObjectMapper jsonObjectMapper;
+    private final MeterRegistry meterRegistry;
 
     @Value("${app.lcm.events.topic:ncmp-events}")
     private String topicName;
@@ -51,24 +60,58 @@ public class LcmEventsService {
     private boolean notificationsEnabled;
 
     /**
-     * Publish the LcmEvent with header to the public topic.
+     * Publishes an LCM event to the dedicated topic with optional notification headers.
+     * Capture and log KafkaException If an error occurs while publishing the event to Kafka
      *
-     * @param cmHandleId     Cm Handle Id
-     * @param lcmEvent       Lcm Event
-     * @param lcmEventHeader Lcm Event Header
+     * @param cmHandleId     Cm Handle Id associated with the LCM event
+     * @param lcmEvent       The LCM event object to be published
+     * @param lcmEventHeader Optional headers associated with the LCM event
      */
-    @Timed(value = "cps.ncmp.lcm.events.publish", description = "Time taken to publish a LCM event")
     public void publishLcmEvent(final String cmHandleId, final LcmEvent lcmEvent, final LcmEventHeader lcmEventHeader) {
+
         if (notificationsEnabled) {
+            final Timer.Sample timerSample = Timer.start(meterRegistry);
             try {
                 final Map<String, Object> lcmEventHeadersMap =
                         jsonObjectMapper.convertToValueType(lcmEventHeader, Map.class);
                 eventsPublisher.publishEvent(topicName, cmHandleId, lcmEventHeadersMap, lcmEvent);
             } catch (final KafkaException e) {
                 log.error("Unable to publish message to topic : {} and cause : {}", topicName, e.getMessage());
+            } finally {
+                recordMetrics(lcmEvent, timerSample);
             }
         } else {
             log.debug("Notifications disabled.");
         }
     }
+
+    private void recordMetrics(final LcmEvent lcmEvent, final Timer.Sample timerSample) {
+        final List<Tag> tags = new ArrayList<>(4);
+        tags.add(TAG_CLASS);
+        tags.add(TAG_METHOD);
+
+        final String oldCmHandleState = extractCmHandleStateValue(lcmEvent.getEvent().getOldValues());
+        tags.add(Tag.of("oldCmHandleState", oldCmHandleState));
+
+        final String newCmHandleState = extractCmHandleStateValue(lcmEvent.getEvent().getNewValues());
+        tags.add(Tag.of("newCmHandleState", newCmHandleState));
+
+        timerSample.stop(Timer.builder("cps.ncmp.lcm.events.publish")
+                .description("Time taken to publish a LCM event")
+                .tags(tags)
+                .register(meterRegistry));
+    }
+
+    /**
+     * Extracts the CM handle state value from the given Values object.
+     * If the provided Values object or its CM handle state is null, returns a default value.
+     *
+     * @param values The Values object containing CM handle state information.
+     * @return The CM handle state value as a string, or a default value if null.
+     */
+    private String extractCmHandleStateValue(final Values values) {
+        return (values != null && values.getCmHandleState() != null)
+                ? values.getCmHandleState().value()
+                : UNAVAILABLE_CM_HANDLE_STATE;
+    }
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.trustlevel;
+package org.onap.cps.ncmp.impl.inventory.trustlevel;
 
 import io.cloudevents.CloudEvent;
 import io.cloudevents.kafka.impl.KafkaHeaders;
 import lombok.RequiredArgsConstructor;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
-import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper;
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel;
 import org.onap.cps.ncmp.events.trustlevel.DeviceTrustLevel;
+import org.onap.cps.ncmp.utils.events.CloudEventMapper;
 import org.springframework.kafka.annotation.KafkaListener;
 import org.springframework.stereotype.Component;
 
 @Component
 @RequiredArgsConstructor
-public class DeviceHeartbeatConsumer {
+public class DeviceTrustLevelMessageConsumer {
 
     private static final String CLOUD_EVENT_ID_HEADER_NAME = "ce_id";
     private final TrustLevelManager trustLevelManager;
 
     /**
-     * Listening the device heartbeats.
+     * Listening to the device trust level updates.
      *
-     * @param deviceHeartbeatConsumerRecord Device Heartbeat record.
+     * @param consumerRecord Device trust level record.
      */
     @KafkaListener(topics = "${app.dmi.device-heartbeat.topic}",
         containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
-    public void heartbeatListener(final ConsumerRecord<String, CloudEvent> deviceHeartbeatConsumerRecord) {
+    public void deviceTrustLevelListener(final ConsumerRecord<String, CloudEvent> consumerRecord) {
 
-        final String cmHandleId = KafkaHeaders.getParsedKafkaHeader(deviceHeartbeatConsumerRecord.headers(),
+        final String cmHandleId = KafkaHeaders.getParsedKafkaHeader(consumerRecord.headers(),
             CLOUD_EVENT_ID_HEADER_NAME);
 
         final DeviceTrustLevel deviceTrustLevel =
-            CloudEventMapper.toTargetEvent(deviceHeartbeatConsumerRecord.value(), DeviceTrustLevel.class);
-        final TrustLevel newDeviceTrustLevel = TrustLevel.valueOf(deviceTrustLevel.getData().getTrustLevel());
-        trustLevelManager.handleUpdateOfDeviceTrustLevel(cmHandleId, newDeviceTrustLevel);
-
+            CloudEventMapper.toTargetEvent(consumerRecord.value(), DeviceTrustLevel.class);
+        final String trustLevelAsString = deviceTrustLevel.getData().getTrustLevel();
+        trustLevelManager.updateCmHandleTrustLevel(cmHandleId, TrustLevel.valueOf(trustLevelAsString));
     }
 
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability;
+package org.onap.cps.ncmp.impl.inventory.trustlevel;
 
 import java.util.Collection;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager;
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel;
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient;
+import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -36,10 +36,10 @@ import org.springframework.stereotype.Service;
 @Slf4j
 @RequiredArgsConstructor
 @Service
-public class DmiPluginWatchDog {
+public class DmiPluginTrustLevelWatchDog {
 
     private final DmiRestClient dmiRestClient;
-    private final NetworkCmProxyDataService networkCmProxyDataService;
+    private final CmHandleQueryService cmHandleQueryService;
     private final TrustLevelManager trustLevelManager;
 
     @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_DMI_PLUGIN)
@@ -52,10 +52,8 @@ public class DmiPluginWatchDog {
      */
     @Scheduled(fixedDelayString = "${ncmp.timers.trust-level.dmi-availability-watchdog-ms:30000}")
     public void checkDmiAvailability() {
-        trustLevelPerDmiPlugin.entrySet().forEach(entry -> {
+        trustLevelPerDmiPlugin.forEach((dmiServiceName, oldDmiTrustLevel) -> {
             final TrustLevel newDmiTrustLevel;
-            final TrustLevel oldDmiTrustLevel = entry.getValue();
-            final String dmiServiceName = entry.getKey();
             final String dmiHealthStatus = getDmiHealthStatus(dmiServiceName);
             log.debug("The health status for dmi-plugin: {} is {}", dmiServiceName, dmiHealthStatus);
 
@@ -68,13 +66,15 @@ public class DmiPluginWatchDog {
                 log.debug("The Dmi Plugin: {} has already the same trust level: {}", dmiServiceName, newDmiTrustLevel);
             } else {
                 final Collection<String> cmHandleIds =
-                    networkCmProxyDataService.getAllCmHandleIdsByDmiPluginIdentifier(dmiServiceName);
-                trustLevelManager.handleUpdateOfDmiTrustLevel(dmiServiceName, cmHandleIds, newDmiTrustLevel);
+                    cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiServiceName);
+                trustLevelManager.updateDmi(dmiServiceName, cmHandleIds, newDmiTrustLevel);
             }
         });
     }
 
-    private String getDmiHealthStatus(final String dmiServiceName) {
-        return dmiRestClient.getDmiHealthStatus(dmiServiceName);
+    private String getDmiHealthStatus(final String dmiServiceBaseUrl) {
+        final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
+                .createUrlTemplateParametersForHealthCheck(dmiServiceBaseUrl);
+        return dmiRestClient.getDmiHealthStatus(urlTemplateParameters).block();
     }
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.config.embeddedcache;
+package org.onap.cps.ncmp.impl.inventory.trustlevel;
 
 import com.hazelcast.config.MapConfig;
 import java.util.Map;
 import org.onap.cps.cache.HazelcastCacheConfig;
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.trustlevel;
+package org.onap.cps.ncmp.impl.inventory.trustlevel;
 
 import java.util.Collection;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.config.embeddedcache.TrustLevelCacheConfig;
-import org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient.AvcEventPublisher;
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration;
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.models.RequiredDmiService;
+import org.onap.cps.ncmp.utils.events.CmAvcEventPublisher;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
@@ -44,16 +45,31 @@ public class TrustLevelManager {
     private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
 
     private final InventoryPersistence inventoryPersistence;
-    private final AvcEventPublisher avcEventPublisher;
+    private final CmAvcEventPublisher cmAvcEventPublisher;
     private static final String AVC_CHANGED_ATTRIBUTE_NAME = "trustLevel";
     private static final String AVC_NO_OLD_VALUE = null;
 
+    /**
+     * Add dmi plugins to the cache.
+     *
+     * @param dmiPluginRegistration a dmi plugin being registered
+     */
+    public void registerDmiPlugin(final DmiPluginRegistration dmiPluginRegistration) {
+        final String dmiServiceName;
+        if (DmiPluginRegistration.isNullEmptyOrBlank(dmiPluginRegistration.getDmiDataPlugin())) {
+            dmiServiceName = dmiPluginRegistration.getDmiPlugin();
+        } else {
+            dmiServiceName = dmiPluginRegistration.getDmiDataPlugin();
+        }
+        trustLevelPerDmiPlugin.put(dmiServiceName, TrustLevel.COMPLETE);
+    }
+
     /**
      * Add cmHandles to the cache and publish notification for initial trust level of cmHandles if it is NONE.
      *
      * @param cmHandlesToBeCreated a list of cmHandles being created
      */
-    public void handleInitialRegistrationOfTrustLevels(final Map<String, TrustLevel> cmHandlesToBeCreated) {
+    public void registerCmHandles(final Map<String, TrustLevel> cmHandlesToBeCreated) {
         for (final Map.Entry<String, TrustLevel> entry : cmHandlesToBeCreated.entrySet()) {
             final String cmHandleId = entry.getKey();
             if (trustLevelPerCmHandle.containsKey(cmHandleId)) {
@@ -65,7 +81,7 @@ public class TrustLevelManager {
                 }
                 trustLevelPerCmHandle.put(cmHandleId, initialTrustLevel);
                 if (TrustLevel.NONE.equals(initialTrustLevel)) {
-                    avcEventPublisher.publishAvcEvent(cmHandleId,
+                    cmAvcEventPublisher.publishAvcEvent(cmHandleId,
                         AVC_CHANGED_ATTRIBUTE_NAME,
                         AVC_NO_OLD_VALUE,
                         initialTrustLevel.name());
@@ -82,15 +98,15 @@ public class TrustLevelManager {
      * @param affectedCmHandleIds   cm handle ids belonging to dmi service name
      * @param newDmiTrustLevel      new trust level of the dmi plugin
      */
-    public void handleUpdateOfDmiTrustLevel(final String dmiServiceName,
-                                            final Collection<String> affectedCmHandleIds,
-                                            final TrustLevel newDmiTrustLevel) {
+    public void updateDmi(final String dmiServiceName,
+                          final Collection<String> affectedCmHandleIds,
+                          final TrustLevel newDmiTrustLevel) {
         final TrustLevel oldDmiTrustLevel  = trustLevelPerDmiPlugin.get(dmiServiceName);
         trustLevelPerDmiPlugin.put(dmiServiceName, newDmiTrustLevel);
         for (final String affectedCmHandleId : affectedCmHandleIds) {
-            final TrustLevel deviceTrustLevel = trustLevelPerCmHandle.get(affectedCmHandleId);
-            final TrustLevel oldEffectiveTrustLevel = deviceTrustLevel.getEffectiveTrustLevel(oldDmiTrustLevel);
-            final TrustLevel newEffectiveTrustLevel = deviceTrustLevel.getEffectiveTrustLevel(newDmiTrustLevel);
+            final TrustLevel cmHandleTrustLevel = trustLevelPerCmHandle.get(affectedCmHandleId);
+            final TrustLevel oldEffectiveTrustLevel = cmHandleTrustLevel.getEffectiveTrustLevel(oldDmiTrustLevel);
+            final TrustLevel newEffectiveTrustLevel = cmHandleTrustLevel.getEffectiveTrustLevel(newDmiTrustLevel);
             sendAvcNotificationIfRequired(affectedCmHandleId, oldEffectiveTrustLevel, newEffectiveTrustLevel);
         }
     }
@@ -100,23 +116,52 @@ public class TrustLevelManager {
      * changed.
      *
      * @param cmHandleId            cm handle id
-     * @param newDeviceTrustLevel   new trust level of the device
+     * @param newCmHandleTrustLevel   new trust level of the device
      */
-    public void handleUpdateOfDeviceTrustLevel(final String cmHandleId,
-                                               final TrustLevel newDeviceTrustLevel) {
-        final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
-        final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA);
+    public void updateCmHandleTrustLevel(final String cmHandleId,
+                                         final TrustLevel newCmHandleTrustLevel) {
+        final String dmiServiceName = getDmiServiceName(cmHandleId);
 
         final TrustLevel dmiTrustLevel = trustLevelPerDmiPlugin.get(dmiServiceName);
-        final TrustLevel oldDeviceTrustLevel = trustLevelPerCmHandle.get(cmHandleId);
+        final TrustLevel oldCmHandleTrustLevel = trustLevelPerCmHandle.get(cmHandleId);
 
-        final TrustLevel oldEffectiveTrustLevel = oldDeviceTrustLevel.getEffectiveTrustLevel(dmiTrustLevel);
-        final TrustLevel newEffectiveTrustLevel = newDeviceTrustLevel.getEffectiveTrustLevel(dmiTrustLevel);
+        final TrustLevel oldEffectiveTrustLevel = oldCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel);
+        final TrustLevel newEffectiveTrustLevel = newCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel);
 
-        trustLevelPerCmHandle.put(cmHandleId, newDeviceTrustLevel);
+        trustLevelPerCmHandle.put(cmHandleId, newCmHandleTrustLevel);
         sendAvcNotificationIfRequired(cmHandleId, oldEffectiveTrustLevel, newEffectiveTrustLevel);
     }
 
+    /**
+     * Select effective trust level among device and dmi plugin.
+     *
+     * @param cmHandleId       cm handle id
+     * @return TrustLevel      effective trust level
+     */
+    public TrustLevel getEffectiveTrustLevel(final String dmiServiceName, final String cmHandleId) {
+        final TrustLevel dmiTrustLevel = trustLevelPerDmiPlugin.get(dmiServiceName);
+        final TrustLevel cmHandleTrustLevel = trustLevelPerCmHandle.get(cmHandleId);
+        return dmiTrustLevel.getEffectiveTrustLevel(cmHandleTrustLevel);
+    }
+
+    /**
+     * Remove cm handle trust level from the cache.
+     *
+     * @param cmHandleIds       cm handle ids to be removed from the cache
+     */
+    public void removeCmHandles(final Collection<String> cmHandleIds) {
+        for (final String cmHandleId : cmHandleIds) {
+            if (trustLevelPerCmHandle.remove(cmHandleId) == null) {
+                log.debug("Removed Cm handle: {} is not in trust level cache", cmHandleId);
+            }
+        }
+    }
+
+    private String getDmiServiceName(final String cmHandleId) {
+        final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
+        return yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA);
+    }
+
     private void sendAvcNotificationIfRequired(final String notificationCandidateCmHandleId,
                                                final TrustLevel oldEffectiveTrustLevel,
                                                final TrustLevel newEffectiveTrustLevel) {
@@ -126,7 +171,7 @@ public class TrustLevelManager {
         } else {
             log.info("The trust level for Cm Handle: {} is now: {} ", notificationCandidateCmHandleId,
                 newEffectiveTrustLevel);
-            avcEventPublisher.publishAvcEvent(notificationCandidateCmHandleId,
+            cmAvcEventPublisher.publishAvcEvent(notificationCandidateCmHandleId,
                 AVC_CHANGED_ATTRIBUTE_NAME,
                 oldEffectiveTrustLevel.name(),
                 newEffectiveTrustLevel.name());
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.impl.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -28,7 +28,8 @@ import java.util.List;
 import java.util.Map;
 import lombok.Builder;
 import lombok.Getter;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @Getter
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.impl.models;
 
 /**
  * Enmm to determine if the required service is for a data or model operation.
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2024 Nordix Foundation.
+ *  Copyright (C) 2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.dmi.rest.stub.config;
+package org.onap.cps.ncmp.impl.policyexecutor;
 
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig;
+import org.onap.cps.ncmp.impl.utils.http.WebClientConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.web.filter.CommonsRequestLoggingFilter;
+import org.springframework.web.reactive.function.client.WebClient;
 
 @Configuration
-public class NcmpRequestLoggingConfig {
+@RequiredArgsConstructor
+public class PolicyExecutorWebClientConfiguration extends WebClientConfiguration {
+
+    private final PolicyExecutorHttpClientConfig policyExecutorHttpClientConfig;
 
     /**
-     * Configuration class to log NCMP request headers and payload.
-     * logged request information before it is processed.
+     * Configures and creates a web client bean for Policy Executor.
+     *
+     * @param webClientBuilder The builder instance to create the WebClient.
+     * @return a WebClient instance configured for Policy Executor.
      */
     @Bean
-    public CommonsRequestLoggingFilter logNcmpRequestInfo() {
-        final CommonsRequestLoggingFilter commonsRequestLoggingFilter = new CommonsRequestLoggingFilter();
-        commonsRequestLoggingFilter.setIncludeHeaders(true);
-        commonsRequestLoggingFilter.setIncludeQueryString(true);
-        commonsRequestLoggingFilter.setIncludePayload(true);
-        commonsRequestLoggingFilter.setMaxPayloadLength(1000);
-        commonsRequestLoggingFilter.setAfterMessagePrefix("NCMP REQUEST DATA: ");
-        return commonsRequestLoggingFilter;
+    public WebClient policyExecutorWebClient(final WebClient.Builder webClientBuilder) {
+        return configureWebClient(webClientBuilder, policyExecutorHttpClientConfig.getAllServices());
     }
 }
-
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java
new file mode 100644 (file)
index 0000000..c526dfb
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.utils;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AlternateIdMatcher {
+
+    private final InventoryPersistence inventoryPersistence;
+
+    /**
+     * Get data node that matches longest alternate id by removing elements (as defined by the separator string)
+     * from right to left.
+     *
+     * @param alternateId alternate ID
+     * @param separator   a string that separates each element from the next.
+     * @return data node
+     */
+    public DataNode getCmHandleDataNodeByLongestMatchingAlternateId(final String alternateId, final String separator) {
+        String bestMatch = alternateId;
+        while (StringUtils.isNotEmpty(bestMatch)) {
+            try {
+                return inventoryPersistence.getCmHandleDataNodeByAlternateId(bestMatch);
+            } catch (final DataNodeNotFoundException ignored) {
+                bestMatch = getParentPath(bestMatch, separator);
+            }
+        }
+        throw new NoAlternateIdMatchFoundException(alternateId);
+    }
+
+    /**
+     * Get cm handle Id from given cmHandleReference.
+     *
+     * @param cmHandleReference cm handle or alternate identifier
+     * @return cm handle id string
+     */
+    public String getCmHandleId(final String cmHandleReference) {
+        if (inventoryPersistence.isExistingCmHandleId(cmHandleReference)) {
+            return cmHandleReference;
+        } else {
+            return inventoryPersistence.getCmHandleDataNodeByAlternateId(cmHandleReference)
+              .getLeaves().get("id").toString();
+        }
+    }
+
+    private String getParentPath(final String path, final String separator) {
+        final int lastSeparatorIndex = path.lastIndexOf(separator);
+        return lastSeparatorIndex < 0 ? "" : path.substring(0, lastSeparatorIndex);
+    }
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils;
+package org.onap.cps.ncmp.impl.utils;
 
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -30,10 +30,10 @@ import java.util.stream.Collectors;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.CompositeState;
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.onap.cps.spi.model.DataNode;
 
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -43,12 +43,12 @@ public class YangDataConverter {
     private static final Pattern cmHandleIdInXpathPattern = Pattern.compile("\\[@id='(.*?)']");
 
     /**
-     * This method convert yang model cm handle to ncmp service cm handle.
+     * This method converts yang model cm handle to ncmp service cm handle.
      * @param yangModelCmHandle the yang model of the cm handle
      * @return ncmp service cm handle
      */
-    public static NcmpServiceCmHandle convertYangModelCmHandleToNcmpServiceCmHandle(
-            final YangModelCmHandle yangModelCmHandle) {
+    public static NcmpServiceCmHandle toNcmpServiceCmHandle(
+        final YangModelCmHandle yangModelCmHandle) {
         final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
         final List<YangModelCmHandle.Property> dmiProperties = yangModelCmHandle.getDmiProperties();
         final List<YangModelCmHandle.Property> publicProperties = yangModelCmHandle.getPublicProperties();
@@ -63,23 +63,24 @@ public class YangDataConverter {
     }
 
     /**
-     * This method convert yang model cm handle properties to simple map.
+     * This method converts yang model cm handle properties to simple map.
      * @param properties the yang model cm handle properties
-     * @param propertiesMap the String, String map for the results
+     * @return simple map representing the properties
      */
-    public static void asPropertiesMap(final List<YangModelCmHandle.Property> properties,
-                                       final Map<String, String> propertiesMap) {
+    public static Map<String, String> toPropertiesMap(final List<YangModelCmHandle.Property> properties) {
+        final Map<String, String> propertiesMap = new LinkedHashMap<>(properties.size());
         for (final YangModelCmHandle.Property property : properties) {
             propertiesMap.put(property.getName(), property.getValue());
         }
+        return propertiesMap;
     }
 
     /**
-     * This method convert cm handle data node to yang model cm handle.
+     * This method converts cm handle data node to yang model cm handle.
      * @param cmHandleDataNode the datanode of the cm handle
      * @return yang model cm handle
      */
-    public static YangModelCmHandle convertCmHandleToYangModel(final DataNode cmHandleDataNode) {
+    public static YangModelCmHandle toYangModelCmHandle(final DataNode cmHandleDataNode) {
         final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
         final String cmHandleId = cmHandleDataNode.getLeaves().get("id").toString();
         ncmpServiceCmHandle.setCmHandleId(cmHandleId);
@@ -96,18 +97,17 @@ public class YangDataConverter {
     }
 
     /**
-     * This method convert cm handle data nodes to yang model cm handles.
+     * This method converts cm handle data nodes to yang model cm handles.
      * @param cmHandleDataNodes the datanode of the cm handle
      * @return yang model cm handles
      */
-    public static Collection<YangModelCmHandle> convertDataNodesToYangModelCmHandles(
+    public static Collection<YangModelCmHandle> toYangModelCmHandles(
             final Collection<DataNode> cmHandleDataNodes) {
-        return cmHandleDataNodes.stream().map(YangDataConverter::convertCmHandleToYangModel)
-                .collect(Collectors.toList());
+        return cmHandleDataNodes.stream().map(YangDataConverter::toYangModelCmHandle).collect(Collectors.toList());
     }
 
     /**
-     * This method extract cm handle id from xpath of data node.
+     * This method extracts cm handle id from xpath of data node.
      * @param xpath for data node of the cm handle
      * @return cm handle Id
      */
@@ -145,15 +145,11 @@ public class YangDataConverter {
 
     private static void setDmiProperties(final List<YangModelCmHandle.Property> dmiProperties,
                                          final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        final Map<String, String> dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size());
-        asPropertiesMap(dmiProperties, dmiPropertiesMap);
-        ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap);
+        ncmpServiceCmHandle.setDmiProperties(toPropertiesMap(dmiProperties));
     }
 
     private static void setPublicProperties(final List<YangModelCmHandle.Property> publicProperties,
                                             final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        final Map<String, String> publicPropertiesMap = new LinkedHashMap<>();
-        asPropertiesMap(publicProperties, publicPropertiesMap);
-        ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap);
+        ncmpServiceCmHandle.setPublicProperties(toPropertiesMap(publicProperties));
     }
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java
new file mode 100644 (file)
index 0000000..c850ca9
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022-2024 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.impl.utils.http;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.util.Strings;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@NoArgsConstructor
+public class RestServiceUrlTemplateBuilder {
+
+    private final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance();
+    private static final String FIXED_PATH_SEGMENT = null;
+    private static final String VERSION_SEGMENT = "v1";
+    private final Map<String, String> pathSegments = new LinkedHashMap<>();
+    private final Map<String, String> queryParameters = new LinkedHashMap<>();
+
+    /**
+     * Static factory method to create a new instance of DmiServiceUrlTemplateBuilder.
+     *
+     * @return a new instance of DmiServiceUrlTemplateBuilder
+     */
+    public static RestServiceUrlTemplateBuilder newInstance() {
+        return new RestServiceUrlTemplateBuilder();
+    }
+
+    /**
+     * Add a fixed pathSegment to the URL.
+     *
+     * @param pathSegment the path segment
+     * @return this builder instance
+     */
+    public RestServiceUrlTemplateBuilder fixedPathSegment(final String pathSegment) {
+        pathSegments.put(pathSegment, FIXED_PATH_SEGMENT);
+        return this;
+    }
+
+    /**
+     * Add a variable pathSegment to the URL.
+     * Do NOT add { } braces. the builder will take care of that
+     *
+     * @param pathSegment the name of the variable path segment (with { and }
+     * @param value       the value to be insert in teh URL for the given variable path segment
+     * @return this builder instance
+     */
+    public RestServiceUrlTemplateBuilder variablePathSegment(final String pathSegment, final String value) {
+        pathSegments.put(pathSegment, value);
+        return this;
+    }
+
+    /**
+     * Add a query parameter to the URL.
+     * Do NOT encode as the builder wil take care of encoding
+     *
+     * @param queryParameterName  the name of the variable
+     * @param queryParameterValue the value of the variable (only Strings are supported).
+     *
+     * @return this builder instance
+     */
+    public RestServiceUrlTemplateBuilder queryParameter(final String queryParameterName,
+                                                        final String queryParameterValue) {
+        if (Strings.isNotBlank(queryParameterValue)) {
+            queryParameters.put(queryParameterName, queryParameterValue);
+        }
+        return this;
+    }
+
+    /**
+     * Constructs a URL template with variables based on the accumulated path segments and query parameters.
+     *
+     * @param serviceBaseUrl the base URL of the service, e.g., "http://dmi-service.com".
+     * @param basePath       the base path of the service
+     * @return a UrlTemplateParameters instance containing the complete URL template and URL variables
+     */
+    public UrlTemplateParameters createUrlTemplateParameters(final String serviceBaseUrl, final String basePath) {
+        this.uriComponentsBuilder.pathSegment(basePath).pathSegment(VERSION_SEGMENT);
+        final Map<String, String> urlTemplateVariables = new HashMap<>();
+
+        pathSegments.forEach((pathSegmentName, variablePathValue) ->  {
+            if (StringUtils.equals(variablePathValue, FIXED_PATH_SEGMENT)) {
+                this.uriComponentsBuilder.pathSegment(pathSegmentName);
+            } else {
+                this.uriComponentsBuilder.pathSegment("{" + pathSegmentName + "}");
+                urlTemplateVariables.put(pathSegmentName, variablePathValue);
+            }
+        });
+
+        queryParameters.forEach((paramName, paramValue) -> {
+            this.uriComponentsBuilder.queryParam(paramName, "{" + paramName + "}");
+            urlTemplateVariables.put(paramName, paramValue);
+        });
+
+        final String urlTemplate = serviceBaseUrl + this.uriComponentsBuilder.build().toUriString();
+        return new UrlTemplateParameters(urlTemplate, urlTemplateVariables);
+    }
+
+    /**
+     * Constructs a URL for a spring actuator health check based on the given base URL.
+     *
+     * @param serviceBaseUrl the base URL of the service, e.g., "http://dmi-service.com".
+     * @return a {@link UrlTemplateParameters} instance containing the complete URL template and empty URL variables,
+     *     suitable for DMI health check.
+     */
+    public UrlTemplateParameters createUrlTemplateParametersForHealthCheck(final String serviceBaseUrl) {
+        this.uriComponentsBuilder.pathSegment("actuator").pathSegment("health");
+
+        final String urlTemplate = serviceBaseUrl + this.uriComponentsBuilder.build().toUriString();
+        return new UrlTemplateParameters(urlTemplate, Collections.emptyMap());
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/UrlTemplateParameters.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/UrlTemplateParameters.java
new file mode 100644 (file)
index 0000000..839af71
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.utils.http;
+
+import java.util.Map;
+
+/**
+ * Represents a URL template with associated variables for dynamic substitution.
+ * This record encapsulates a URL template string and a map of variables used for substitution within the template.
+ */
+public record UrlTemplateParameters(String urlTemplate, Map<String, String> urlVariables) {
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java
new file mode 100644 (file)
index 0000000..d8e8350
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.utils.http;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import io.netty.channel.ChannelOption;
+import io.netty.handler.timeout.ReadTimeoutHandler;
+import io.netty.handler.timeout.WriteTimeoutHandler;
+import io.netty.resolver.DefaultAddressResolverGroup;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import org.onap.cps.ncmp.config.ServiceConfig;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.resources.ConnectionProvider;
+
+/**
+ * Configures and creates WebClient beans for various rest services such as DMI and Policy Executor.
+ * The configuration utilizes Netty-based HttpClient with custom connection settings, read and write timeouts,
+ * and initializes WebClient with these settings to ensure optimal performance and resource management.
+ */
+public class WebClientConfiguration {
+
+    private static final Duration DEFAULT_RESPONSE_TIMEOUT = Duration.ofSeconds(30);
+
+    protected WebClient configureWebClient(final WebClient.Builder webClientBuilder,
+                                           final ServiceConfig serviceConfig) {
+        final ConnectionProvider connectionProvider = getConnectionProvider(serviceConfig);
+        final HttpClient httpClient = createHttpClient(serviceConfig, connectionProvider);
+        return buildAndGetWebClient(webClientBuilder, httpClient, serviceConfig.getMaximumInMemorySizeInMegabytes());
+    }
+
+    private static HttpClient createHttpClient(final ServiceConfig serviceConfig,
+                                               final ConnectionProvider connectionProvider) {
+        return HttpClient.create(connectionProvider)
+                .responseTimeout(DEFAULT_RESPONSE_TIMEOUT)
+                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serviceConfig.getConnectionTimeoutInSeconds() * 1000)
+                .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(
+                        serviceConfig.getReadTimeoutInSeconds(), TimeUnit.SECONDS)).addHandlerLast(
+                        new WriteTimeoutHandler(serviceConfig.getWriteTimeoutInSeconds(), TimeUnit.SECONDS)))
+                .resolver(DefaultAddressResolverGroup.INSTANCE)
+                .compress(true);
+    }
+
+    @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
+    private static ConnectionProvider getConnectionProvider(final ServiceConfig serviceConfig) {
+        return ConnectionProvider.builder(serviceConfig.getConnectionProviderName())
+                .maxConnections(serviceConfig.getMaximumConnectionsTotal())
+                .pendingAcquireMaxCount(serviceConfig.getPendingAcquireMaxCount())
+                .build();
+    }
+
+    private WebClient buildAndGetWebClient(final WebClient.Builder webClientBuilder, final HttpClient httpClient,
+                                           final int maximumInMemorySizeInMegabytes) {
+        return webClientBuilder
+                .defaultHeaders(header -> header.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))
+                .defaultHeaders(header -> header.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE))
+                .clientConnector(new ReactorClientHttpConnector(httpClient))
+                .codecs(configurer -> configurer.defaultCodecs()
+                        .maxInMemorySize(maximumInMemorySizeInMegabytes * 1024 * 1024)).build();
+    }
+}
index 4cc8cda..6d51eb4 100644 (file)
@@ -33,13 +33,13 @@ import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsDataspaceService;
 import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException;
+import org.onap.cps.ncmp.exceptions.NcmpStartUpException;
 import org.onap.cps.spi.CascadeDeleteAllowed;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.SpringApplication;
-import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -61,12 +61,12 @@ abstract class AbstractModelLoader implements ModelLoader {
     long retryTimeMs;
 
     @Override
-    public void onApplicationEvent(@NonNull final ApplicationReadyEvent applicationReadyEvent) {
+    public void onApplicationEvent(@NonNull final ApplicationStartedEvent applicationStartedEvent) {
         try {
             onboardOrUpgradeModel();
         } catch (final NcmpStartUpException ncmpStartUpException) {
             log.error("Onboarding model for NCMP failed: {} ", ncmpStartUpException.getMessage());
-            SpringApplication.exit(applicationReadyEvent.getApplicationContext(), () -> EXIT_CODE_ON_ERROR);
+            SpringApplication.exit(applicationStartedEvent.getApplicationContext(), () -> EXIT_CODE_ON_ERROR);
         }
     }
 
index a0b7bd5..780b240 100644 (file)
@@ -20,7 +20,7 @@
 
 package org.onap.cps.ncmp.init;
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME;
 import static org.onap.cps.utils.ContentType.JSON;
 
 import java.time.OffsetDateTime;
@@ -29,8 +29,8 @@ import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsDataspaceService;
 import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException;
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
+import org.onap.cps.ncmp.api.data.models.DatastoreType;
+import org.onap.cps.ncmp.exceptions.NcmpStartUpException;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.springframework.stereotype.Service;
 
index 7c25953..76d12f2 100644 (file)
@@ -20,9 +20,9 @@
 
 package org.onap.cps.ncmp.init;
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
 
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsAnchorService;
index c61bf1c..9832ba3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 package org.onap.cps.ncmp.init;
 
 import lombok.NonNull;
-import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
 import org.springframework.context.ApplicationListener;
 
-public interface ModelLoader extends ApplicationListener<ApplicationReadyEvent> {
+public interface ModelLoader extends ApplicationListener<ApplicationStartedEvent> {
 
     @Override
-    void onApplicationEvent(@NonNull ApplicationReadyEvent applicationReadyEvent);
+    void onApplicationEvent(@NonNull ApplicationStartedEvent applicationStartedEvent);
 
     void onboardOrUpgradeModel();
 
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.mapper;
+package org.onap.cps.ncmp.utils.events;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.cloudevents.CloudEvent;
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient;
+package org.onap.cps.ncmp.utils.events;
 
 import io.cloudevents.CloudEvent;
 import java.util.Collections;
@@ -26,7 +26,6 @@ import java.util.HashMap;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import org.onap.cps.events.EventsPublisher;
-import org.onap.cps.ncmp.api.impl.events.NcmpEvent;
 import org.onap.cps.ncmp.events.avc.ncmp_to_client.Avc;
 import org.onap.cps.ncmp.events.avc.ncmp_to_client.AvcEvent;
 import org.onap.cps.ncmp.events.avc.ncmp_to_client.Data;
@@ -35,7 +34,7 @@ import org.springframework.stereotype.Service;
 
 @Service
 @RequiredArgsConstructor
-public class AvcEventPublisher {
+public class CmAvcEventPublisher {
 
     private final EventsPublisher<CloudEvent> eventsPublisher;
 
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events;
+package org.onap.cps.ncmp.utils.events;
 
 import io.cloudevents.CloudEvent;
 import io.cloudevents.core.builder.CloudEventBuilder;
@@ -27,8 +27,8 @@ import java.util.Map;
 import java.util.UUID;
 import lombok.Builder;
 import org.apache.commons.lang3.StringUtils;
-import org.onap.cps.ncmp.api.impl.utils.EventDateTimeFormatter;
-import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext;
+import org.onap.cps.ncmp.config.CpsApplicationContext;
+import org.onap.cps.ncmp.impl.utils.EventDateTimeFormatter;
 import org.onap.cps.utils.JsonObjectMapper;
 
 @Builder
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.util;
+package org.onap.cps.ncmp.utils.events;
 
 import java.util.regex.Pattern;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
-import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException;
+import org.onap.cps.ncmp.api.exceptions.InvalidTopicException;
 
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class TopicValidator {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy
new file mode 100644 (file)
index 0000000..3a6737d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.data.models
+
+import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException
+import spock.lang.Specification
+
+class DatastoreTypeSpec extends Specification {
+
+    def 'Converting string to enum.'() {
+        expect: 'converting string to enum results in the correct enum value'
+            DatastoreType.fromDatastoreName(datastoreName) == expectedEnum
+        where: 'the following datastore names are used'
+            datastoreName                            || expectedEnum
+            'ncmp-datastore:operational'             || DatastoreType.OPERATIONAL
+            'ncmp-datastore:passthrough-running'     || DatastoreType.PASSTHROUGH_RUNNING
+            'ncmp-datastore:passthrough-operational' || DatastoreType.PASSTHROUGH_OPERATIONAL
+    }
+
+    def 'Converting unknown name string to enum.'() {
+        when: 'attempt converting unknown datastore name'
+            DatastoreType.fromDatastoreName('unknown')
+        then: 'an invalid datastore exception is thrown'
+            def thrown = thrown(InvalidDatastoreException)
+            assert thrown.message.contains('unknown is an invalid datastore')
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy
new file mode 100644 (file)
index 0000000..efe67ab
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.data.models
+
+import org.onap.cps.ncmp.api.data.exceptions.InvalidOperationException
+import spock.lang.Specification
+
+class OperationTypeSpec extends Specification {
+
+    def 'Converting string to enum.'() {
+        expect: 'converting string to enum results in the correct enum value'
+            OperationType.fromOperationName(operationName) == expectedEnum
+        where: 'the following datastore names are used'
+            operationName || expectedEnum
+            'read'        || OperationType.READ
+            'create'      || OperationType.CREATE
+            'update'      || OperationType.UPDATE
+            'patch'       || OperationType.PATCH
+            'delete'      || OperationType.DELETE
+    }
+
+    def 'Converting unknown name string to enum.'() {
+        when: 'attempt converting unknown datastore name'
+            OperationType.fromOperationName('unknown')
+        then: 'an invalid operation exception is thrown'
+            def thrown = thrown(InvalidOperationException)
+            assert thrown.message.contains('unknown is an invalid operation')
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DataJobServiceImplSpec.groovy
deleted file mode 100644 (file)
index 4378764..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl
-
-import ch.qos.logback.classic.Level
-import ch.qos.logback.classic.Logger
-import ch.qos.logback.classic.spi.ILoggingEvent
-import ch.qos.logback.core.read.ListAppender
-import org.slf4j.LoggerFactory
-import org.onap.cps.ncmp.api.models.datajob.DataJobReadRequest
-import org.onap.cps.ncmp.api.models.datajob.DataJobWriteRequest
-import org.onap.cps.ncmp.api.models.datajob.DataJobMetadata
-import org.onap.cps.ncmp.api.models.datajob.ReadOperation
-import org.onap.cps.ncmp.api.models.datajob.WriteOperation
-import spock.lang.Specification
-
-class DataJobServiceImplSpec extends Specification{
-
-    def objectUnderTest = new DataJobServiceImpl()
-
-    def logger = Spy(ListAppender<ILoggingEvent>)
-
-    def setup() {
-        setupLogger()
-    }
-
-    def cleanup() {
-        ((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class)).detachAndStopAllAppenders()
-    }
-
-    def '#operation data job request.'() {
-        given: 'data job metadata'
-            def dataJobMetadata = new DataJobMetadata('client-topic', 'application/vnd.3gpp.object-tree-hierarchical+json', 'application/3gpp-json-patch+json')
-        when: 'read/write data job request is processed'
-            if (operation == 'read') {
-                objectUnderTest.readDataJob('some-job-id', dataJobMetadata, new DataJobReadRequest([getWriteOrReadOperationRequest(operation)]))
-            } else {
-                objectUnderTest.writeDataJob('some-job-id', dataJobMetadata, new DataJobWriteRequest([getWriteOrReadOperationRequest(operation)]))
-            }
-        then: 'the data job id is correctly logged'
-            def loggingEvent = logger.list[0]
-            assert loggingEvent.level == Level.INFO
-            assert loggingEvent.formattedMessage.contains('data job id for ' + operation + ' operation is: some-job-id')
-        where: 'the following data job operations are used'
-            operation << ['read', 'write']
-    }
-
-    def getWriteOrReadOperationRequest(operation) {
-        if (operation == 'write') {
-            return new WriteOperation('some/write/path', 'add', 'some-operation-id', 'some-value')
-        }
-        return new ReadOperation('some/read/path', 'read', 'some-operation-id', ['some-attrib-1'], ['some-field-1'], 'some-filter', 'some-scope-type', 1)
-    }
-
-    def setupLogger() {
-        def setupLogger = ((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class))
-        setupLogger.setLevel(Level.DEBUG)
-        setupLogger.addAppender(logger)
-        logger.start()
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
deleted file mode 100644 (file)
index 4d0af6f..0000000
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation
- *  Modifications Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2021-2022 Bell Canada
- *  Modifications Copyright (C) 2023 TechMahindra Ltd.
- *  ================================================================================
- *  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.impl
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
-
-import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse
-import org.onap.cps.ncmp.api.models.CmResourceAddress
-import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
-import com.hazelcast.map.IMap
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
-import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.models.DataOperationDefinition
-import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
-import org.onap.cps.ncmp.api.models.ConditionApiProperties
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.ncmp.api.models.DataOperationRequest
-import org.onap.cps.spi.exceptions.CpsException
-import org.onap.cps.spi.model.ConditionProperties
-import java.util.stream.Collectors
-import org.onap.cps.utils.JsonObjectMapper
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.api.CpsDataService
-import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
-import org.onap.cps.spi.FetchDescendantsOption
-import org.onap.cps.spi.model.DataNode
-import org.springframework.http.HttpStatus
-import org.springframework.http.ResponseEntity
-import spock.lang.Specification
-
-class NetworkCmProxyDataServiceImplSpec extends Specification {
-
-    def mockCpsDataService = Mock(CpsDataService)
-    def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
-    def mockDmiDataOperations = Mock(DmiDataOperations)
-    def nullNetworkCmProxyDataServicePropertyHandler = null
-    def mockInventoryPersistence = Mock(InventoryPersistence)
-    def mockCmHandleQueries = Mock(CmHandleQueries)
-    def mockDmiPluginRegistration = Mock(DmiPluginRegistration)
-    def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
-    def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
-    def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
-    def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>)
-    def mockTrustLevelManager = Mock(TrustLevelManager)
-    def mockAlternateIdChecker = Mock(AlternateIdChecker)
-
-    def NO_TOPIC = null
-    def NO_REQUEST_ID = null
-    def NO_AUTH_HEADER = null
-    def OPTIONS_PARAM = '(a=1,b=2)'
-    def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
-
-    def objectUnderTest = new NetworkCmProxyDataServiceImpl(
-            spiedJsonObjectMapper,
-            mockDmiDataOperations,
-            nullNetworkCmProxyDataServicePropertyHandler,
-            mockInventoryPersistence,
-            mockCmHandleQueries,
-            mockCpsCmHandlerQueryService,
-            mockLcmEventsCmHandleStateHandler,
-            mockCpsDataService,
-            stubModuleSyncStartedOnCmHandles,
-            stubTrustLevelPerDmiPlugin,
-            mockTrustLevelManager,
-            mockAlternateIdChecker)
-
-    def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
-
-    def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
-
-    def 'Write resource data for pass-through running from DMI using POST.'() {
-        given: 'cpsDataService returns valid datanode'
-            mockDataNode()
-        when: 'write resource data is called'
-            objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                'testResourceId', CREATE,
-                '{some-json}', 'application/json', null)
-        then: 'DMI called with correct data'
-            1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
-                    CREATE, '{some-json}', 'application/json', null)
-                >> { new ResponseEntity<>(HttpStatus.CREATED) }
-    }
-
-    def 'Get resource data for from DMI.'() {
-        given: 'cpsDataService returns valid data node'
-            mockDataNode()
-        and: 'some cm resource address'
-            def cmResourceAddress = new CmResourceAddress('some datastore','some CM Handle', 'some resource Id')
-        and: 'get resource data from DMI is called'
-            mockDmiDataOperations.getResourceDataFromDmi(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >>
-                    new ResponseEntity<>('dmi-response', HttpStatus.OK)
-        when: 'get resource data operational for the given cm resource address is called'
-            def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
-        then: 'DMI returns a json response'
-            assert response == 'dmi-response'
-    }
-
-    def 'Get resource data for operational (cached) data.'() {
-        given: 'CPS Data service returns some object(s)'
-            mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object']
-        and: 'a cm resource address for the same datastore, cm handle and resource id'
-            def cmResourceAddress = new CmResourceAddress(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId')
-        when: 'get resource data is called'
-            def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, FetchDescendantsOption.OMIT_DESCENDANTS)
-        then: 'get resource data returns teh first object from the data service'
-            assert response == 'First Object'
-    }
-
-    def 'Execute (async) data operation for #datastoreName from DMI.'() {
-        given: 'cpsDataService returns valid data node'
-            def dataOperationRequest = getDataOperationRequest(datastoreName)
-        when: 'request resource data for data operation is called'
-            objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
-        then: 'request resource data for data operation returns expected response'
-            1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
-        where: 'the following data stores are used'
-            datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
-    }
-
-    def 'Getting Yang Resources.'() {
-        when: 'yang resources is called'
-            objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
-        then: 'CPS module services is invoked for the correct dataspace and cm handle'
-            1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
-    }
-
-    def 'Get a cm handle.'() {
-        given: 'the system returns a yang modelled cm handle'
-            def dmiServiceName = 'some service name'
-            def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
-                    lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
-                    lastUpdateTime: 'some-timestamp',
-                    dataSyncEnabled: false,
-                    dataStores: dataStores())
-            def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
-            def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
-            def moduleSetTag = 'some-module-set-tag'
-            def alternateId = 'some-alternate-id'
-            def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
-                    dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState,
-                    moduleSetTag: moduleSetTag, alternateId: alternateId)
-            1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
-        when: 'getting cm handle details for a given cm handle id from ncmp service'
-            def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
-        then: 'the result is a ncmpServiceCmHandle'
-            result.class == NcmpServiceCmHandle.class
-        and: 'the cm handle contains the cm handle id'
-            result.cmHandleId == 'some-cm-handle'
-        and: 'the cm handle contains the alternate id'
-            result.alternateId == 'some-alternate-id'
-        and: 'the cm handle contains the module-set-tag'
-            result.moduleSetTag == 'some-module-set-tag'
-        and: 'the cm handle contains the DMI Properties'
-            result.dmiProperties ==[ Book:'Romance Novel' ]
-        and: 'the cm handle contains the public Properties'
-            result.publicProperties == [ "Public Book":'Public Romance Novel' ]
-        and: 'the cm handle contains the cm handle composite state'
-            result.compositeState == compositeState
-    }
-
-    def 'Get cm handle public properties'() {
-        given: 'a yang modelled cm handle'
-            def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
-            def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
-            def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
-        and: 'the system returns this yang modelled cm handle'
-            1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
-        when: 'getting cm handle public properties for a given cm handle id from ncmp service'
-            def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
-        then: 'the result returns the correct data'
-            result == [ 'public prop' : 'some public prop' ]
-    }
-
-    def 'Execute cm handle id search for inventory'() {
-        given: 'a ConditionApiProperties object'
-            def conditionProperties = new ConditionProperties()
-            conditionProperties.conditionName = 'hasAllProperties'
-            conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
-            def conditionServiceProps = new CmHandleQueryServiceParameters()
-            conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
-        and: 'the system returns an set of cmHandle ids'
-            mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
-        when: 'getting cm handle id set for a given dmi property'
-            def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
-        then: 'the result returns the correct 2 elements'
-            assert result.size() == 2
-            assert result.contains('cmHandle1')
-            assert result.contains('cmHandle2')
-    }
-
-    def 'Get cm handle composite state'() {
-        given: 'a yang modelled cm handle'
-            def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
-                lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
-                lastUpdateTime: 'some-timestamp',
-                dataSyncEnabled: false,
-                dataStores: dataStores())
-            def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
-            def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
-            def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
-        and: 'the system returns this yang modelled cm handle'
-            1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
-        when: 'getting cm handle composite state for a given cm handle id from ncmp service'
-            def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
-        then: 'the result returns the correct data'
-            result == compositeState
-    }
-
-    def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
-        given: 'cpsDataService returns valid datanode'
-            mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
-        when: 'get resource data is called'
-            objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                'testResourceId', UPDATE,
-                '{some-json}', 'application/json', NO_AUTH_HEADER)
-        then: 'DMI called with correct data'
-            1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
-                    UPDATE, '{some-json}', 'application/json', NO_AUTH_HEADER)
-                >> { new ResponseEntity<>(HttpStatus.OK) }
-    }
-
-    def 'Verify modules and create anchor params.'() {
-        given: 'dmi plugin registration return created cm handles'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
-                    dmiDataPlugin: 'service2')
-            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
-            mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
-        and: 'no rejected cm handles because of alternate ids'
-            mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> []
-        when: 'parse and create cm handle in dmi registration then sync module'
-            mockDmiPluginRegistration.createdCmHandles = ['test-cm-handle-id']
-            objectUnderTest.processCreatedCmHandles(mockDmiPluginRegistration, new DmiPluginRegistrationResponse())
-        then: 'system persists the cm handle state'
-            1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> {
-                args -> {
-                          def cmHandleStatePerCmHandle = (args[0] as Collection)
-                          cmHandleStatePerCmHandle.each {
-                            assert it.id == 'test-cm-handle-id'
-                          }
-                    }
-            }
-    }
-
-    def 'Execute cm handle id search'() {
-        given: 'valid CmHandleQueryApiParameters input'
-            def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
-            def conditionApiProperties = new ConditionApiProperties()
-            conditionApiProperties.conditionName = 'hasAllModules'
-            conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
-            cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
-        and: 'query cm handle method return with a data node list'
-            mockCpsCmHandlerQueryService.queryCmHandleIds(
-                    spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
-                    >> ['cm-handle-id-1']
-        when: 'execute cm handle search is called'
-            def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
-        then: 'result is the same collection as returned by the CPS Data Service'
-            assert result == ['cm-handle-id-1']
-    }
-
-    def 'Getting module definitions by module'() {
-        when: 'get module definitions is performed with module name'
-            objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
-        then: 'ncmp inventory persistence service is invoked once with correct parameters'
-            1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
-    }
-
-    def 'Getting module definitions by cm handle id'() {
-        when: 'get module definitions is performed with cm handle id'
-            objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
-        then: 'ncmp inventory persistence service is invoked once with correct parameter'
-            1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
-    }
-
-    def 'Execute cm handle search'() {
-        given: 'valid CmHandleQueryApiParameters input'
-            def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
-            def conditionApiProperties = new ConditionApiProperties()
-            conditionApiProperties.conditionName = 'hasAllModules'
-            conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
-            cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
-        and: 'query cm handle method return with a data node list'
-            mockCpsCmHandlerQueryService.queryCmHandles(
-                    spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
-                    >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
-        when: 'execute cm handle search is called'
-            def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
-        then: 'result is the same collection as returned by the CPS Data Service'
-            assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
-    }
-
-    def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is  #scenario'() {
-        given: 'an existing cm handle composite state'
-            def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
-                dataStores: CompositeState.DataStores.builder()
-                    .operationalDataStore(CompositeState.Operational.builder()
-                        .dataStoreSyncState(initialDataSyncState)
-                        .build()).build())
-        and: 'get cm handle state returns the composite state for the given cm handle id'
-            mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
-        when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
-            objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
-        then: 'the data sync enabled flag is set to #dataSyncEnabled'
-            compositeState.dataSyncEnabled == dataSyncEnabledFlag
-        and: 'the data store sync state is set to #expectedDataStoreSyncState'
-            compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
-        and: 'the cps data service to delete data nodes is invoked the expected number of times'
-            deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _)
-        and: 'the inventory persistence service to update node leaves is called with the correct values'
-            saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
-        where: 'the following data sync enabled flag is used'
-            scenario                                              | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState               || expectedDataStoreSyncState         | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
-            'enabled'                                             | true                | false                      | DataStoreSyncState.NONE_REQUESTED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 1
-            'disabled'                                            | false               | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.NONE_REQUESTED  | 0                                        | 1
-            'disabled where sync-state is currently SYNCHRONIZED' | false               | true                       | DataStoreSyncState.SYNCHRONIZED    || DataStoreSyncState.NONE_REQUESTED  | 1                                        | 1
-            'is set to existing flag state'                       | true                | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 0
-    }
-
-    def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
-        given: 'a cm handle composite state'
-            def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
-        and: 'get cm handle state returns the composite state for the given cm handle id'
-            mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
-        when: 'set data sync enabled is called with the data sync enabled flag set to true'
-            objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
-        then: 'the expected exception is thrown'
-            thrown(CpsException)
-        and: 'the inventory persistence service to update node leaves is not invoked'
-            0 * mockInventoryPersistence.saveCmHandleState(_, _)
-    }
-
-    def 'Get all cm handle IDs by DMI plugin identifier.' () {
-        given: 'cm handle queries service returns cm handles'
-            1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
-        when: 'cm handle Ids are requested with dmi plugin identifier'
-            def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
-        then: 'the result size is correct'
-            assert result.size() == 2
-        and: 'the result returns the correct details'
-            assert result.containsAll('cm-handle-1','cm-handle-2')
-    }
-
-    def dataStores() {
-        CompositeState.DataStores.builder()
-            .operationalDataStore(CompositeState.Operational.builder()
-                .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
-                .lastSyncTime('some-timestamp').build()).build()
-    }
-
-    def mockDataNode() {
-        mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
-    }
-
-    def getDataOperationRequest(datastore) {
-        def dataOperationRequest = new DataOperationRequest()
-        def dataOperationDefinitions = new ArrayList()
-        dataOperationDefinitions.add(getDataOperationDefinition(datastore))
-        dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions)
-        return dataOperationRequest
-    }
-
-    def getDataOperationDefinition(datastore) {
-        def dataOperationDefinition = new DataOperationDefinition()
-        dataOperationDefinition.setOperation("read")
-        dataOperationDefinition.setOperationId("operational-12")
-        dataOperationDefinition.setDatastore(datastore)
-        def targetIds = new ArrayList()
-        targetIds.add("some-cm-handle")
-        dataOperationDefinition.setCmHandleIds(targetIds)
-        return dataOperationDefinition
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
deleted file mode 100644 (file)
index c8e34b1..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
- *  Modifications 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.impl.client
-
-import com.fasterxml.jackson.databind.JsonNode
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.databind.node.ObjectNode
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
-import org.onap.cps.ncmp.utils.TestUtils
-import org.spockframework.spring.SpringBean
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.http.HttpEntity
-import org.springframework.http.HttpHeaders
-import org.springframework.http.HttpStatus
-import org.springframework.http.ResponseEntity
-import org.springframework.test.context.ContextConfiguration
-import org.springframework.web.client.HttpServerErrorException
-import org.springframework.web.client.RestTemplate
-import spock.lang.Specification
-
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
-
-@SpringBootTest
-@ContextConfiguration(classes = [DmiProperties, DmiRestClient, ObjectMapper])
-class DmiRestClientSpec extends Specification {
-
-    static final NO_AUTH_HEADER = null
-    static final BASIC_AUTH_HEADER = 'Basic c29tZS11c2VyOnNvbWUtcGFzc3dvcmQ='
-    static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token'
-
-    @SpringBean
-    RestTemplate mockRestTemplate = Mock(RestTemplate)
-
-    @Autowired
-    NcmpConfiguration.DmiProperties dmiProperties
-
-    @Autowired
-    DmiRestClient objectUnderTest
-
-    @Autowired
-    ObjectMapper objectMapper
-
-    def responseFromRestTemplate = Mock(ResponseEntity)
-
-    def 'DMI POST operation with JSON.'() {
-        given: 'the rest template returns a valid response entity for the expected parameters'
-            mockRestTemplate.postForEntity('my url', _ as HttpEntity, Object.class) >> responseFromRestTemplate
-        when: 'POST operation is invoked'
-            def result = objectUnderTest.postOperationWithJsonData('my url', 'some json', READ, null)
-        then: 'the output of the method is equal to the output from the test template'
-            result == responseFromRestTemplate
-    }
-
-    def 'Failing DMI POST operation.'() {
-        given: 'the rest template returns a valid response entity'
-            def serverResponse = 'server response'.getBytes()
-            def httpServerErrorException = new HttpServerErrorException(HttpStatus.FORBIDDEN, 'status text', serverResponse, null)
-            mockRestTemplate.postForEntity(*_) >> { throw httpServerErrorException }
-        when: 'POST operation is invoked'
-            def result = objectUnderTest.postOperationWithJsonData('some url', 'some json', operation, null)
-        then: 'a Http Client Exception is thrown'
-            def thrown = thrown(HttpClientRequestException)
-        and: 'the exception has the relevant details from the error response'
-            assert thrown.httpStatus == 403
-            assert thrown.message == "Unable to ${operation} resource data."
-            assert thrown.details == 'server response'
-        where: 'the following operation is executed'
-            operation << [CREATE, READ, PATCH]
-    }
-
-    def 'Dmi trust level is determined by spring boot health status'() {
-        given: 'a health check response'
-            def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json')
-            def jsonNode = objectMapper.readValue(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
-            ((ObjectNode) jsonNode).put('status', 'my status')
-            mockRestTemplate.getForObject(*_) >> {jsonNode}
-        when: 'get trust level of the dmi plugin'
-            def result = objectUnderTest.getDmiHealthStatus('some url')
-        then: 'the status value from the json is return'
-            assert result == 'my status'
-    }
-
-    def 'Failing to get dmi plugin health status #scenario'() {
-        given: 'rest template with #scenario'
-            mockRestTemplate.getForObject(*_) >> healthStatusResponse
-        when: 'attempt to get health status of the dmi plugin'
-            def result = objectUnderTest.getDmiHealthStatus('some url')
-        then: 'result will be empty'
-            assert result == ''
-        where: 'the following responses are used'
-            scenario    | healthStatusResponse
-            'null'      | null
-            'exception' | {throw new Exception()}
-    }
-
-    def 'DMI auth header #scenario'() {
-        when: 'Specific dmi properties are provided'
-            dmiProperties.dmiBasicAuthEnabled = authEnabled
-        then: 'http headers to conditionally have Authorization header'
-            def authHeaderValues = objectUnderTest.configureHttpHeaders(new HttpHeaders(), ncmpAuthHeader).getOrEmpty('Authorization')
-            def outputAuthHeader = (authHeaderValues == null ? null : authHeaderValues[0])
-            assert outputAuthHeader == expectedAuthHeader
-        where: 'the following configurations are used'
-            scenario                                          | authEnabled | ncmpAuthHeader     || expectedAuthHeader
-            'DMI basic auth enabled, no NCMP bearer token'    | true        | NO_AUTH_HEADER     || BASIC_AUTH_HEADER
-            'DMI basic auth enabled, with NCMP bearer token'  | true        | BEARER_AUTH_HEADER || BASIC_AUTH_HEADER
-            'DMI basic auth disabled, no NCMP bearer token'   | false       | NO_AUTH_HEADER     || NO_AUTH_HEADER
-            'DMI basic auth disabled, with NCMP bearer token' | false       | BEARER_AUTH_HEADER || BEARER_AUTH_HEADER
-            'DMI basic auth disabled, with NCMP basic auth'   | false       | BASIC_AUTH_HEADER  || NO_AUTH_HEADER
-    }
-
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy
deleted file mode 100644 (file)
index 74e3424..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 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.impl.config
-
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.boot.web.client.RestTemplateBuilder
-import org.springframework.http.MediaType
-import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
-import org.springframework.test.context.ContextConfiguration
-import org.springframework.web.client.RestTemplate
-import spock.lang.Specification
-
-@SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, HttpClientConfiguration])
-class NcmpConfigurationSpec extends Specification{
-
-    @Autowired
-    NcmpConfiguration.DmiProperties dmiProperties
-
-    @Autowired
-    HttpClientConfiguration httpClientConfiguration
-
-    def mockRestTemplateBuilder = new RestTemplateBuilder()
-
-    def 'NcmpConfiguration Construction.'() {
-        expect: 'the system can create an instance'
-             new NcmpConfiguration() != null
-    }
-
-    def 'DMI Properties.'() {
-        expect: 'properties are set to values in test configuration yaml file'
-            dmiProperties.authUsername == 'some-user'
-            dmiProperties.authPassword == 'some-password'
-    }
-
-    def 'Rest Template creation with CloseableHttpClient and MappingJackson2HttpMessageConverter.'() {
-        when: 'a rest template is created'
-            def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder, httpClientConfiguration)
-        then: 'the rest template is returned'
-            assert result instanceof RestTemplate
-        and: 'the rest template is created with httpclient5'
-            assert result.getRequestFactory() instanceof HttpComponentsClientHttpRequestFactory
-            assert ((HttpComponentsClientHttpRequestFactory) result.getRequestFactory()).getHttpClient() instanceof CloseableHttpClient;
-        and: 'a jackson media converter has been added'
-            def lastMessageConverter = result.getMessageConverters().get(result.getMessageConverters().size()-1)
-            lastMessageConverter instanceof MappingJackson2HttpMessageConverter
-        and: 'the jackson media converters supports the expected media types'
-            lastMessageConverter.getSupportedMediaTypes() == [MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN];
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy
deleted file mode 100644 (file)
index e506526..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (c) 2024 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.impl.events.cmsubscription
-
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType
-import spock.lang.Specification
-
-class CmNotificationSubscriptionDeltaSpec extends Specification {
-
-    def mockCmNotificationSubscriptionPersistenceService = Mock(CmNotificationSubscriptionPersistenceService)
-    def objectUnderTest = new CmNotificationSubscriptionDelta(mockCmNotificationSubscriptionPersistenceService)
-
-    def 'Find Delta of given list of predicates'() {
-        given: 'A list of predicates'
-            def predicateList = [new DmiCmNotificationSubscriptionPredicate(['ch-1','ch-2'].toSet(), DatastoreType.PASSTHROUGH_OPERATIONAL, ['a/1/','b/2'].toSet())]
-        and: '3 positive responses and 1 negative.'
-            mockCmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_OPERATIONAL, 'ch-1', 'a/1/') >>> true
-            mockCmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_OPERATIONAL, 'ch-1', 'b/2') >>> true
-            mockCmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_OPERATIONAL, 'ch-2', 'a/1/') >>> true
-            mockCmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_OPERATIONAL, 'ch-2', 'b/2') >>> false
-        when: 'getDelta is called'
-            def result = objectUnderTest.getDelta(predicateList)
-        then: 'verify correct delta is returned'
-            assert result.size() == 1
-            assert result[0].targetCmHandleIds[0] == 'ch-2'
-            assert result[0].xpaths[0] == 'b/2'
-
-    }
-
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandlerSpec.groovy
deleted file mode 100644 (file)
index 788a7a7..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (c) 2024 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.impl.events.cmsubscription
-
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent
-import spock.lang.Specification
-
-class CmNotificationSubscriptionEventsHandlerSpec extends Specification {
-
-    def mockCmNotificationSubscriptionNcmpOutEventProducer = Mock(CmNotificationSubscriptionNcmpOutEventProducer)
-    def mockCmNotificationSubscriptionDmiInEventProducer = Mock(CmNotificationSubscriptionDmiInEventProducer)
-
-    def objectUnderTest = new CmNotificationSubscriptionEventsHandler(mockCmNotificationSubscriptionNcmpOutEventProducer,
-        mockCmNotificationSubscriptionDmiInEventProducer)
-
-    def 'Publish cm notification subscription ncmp out event'() {
-        given: 'an ncmp out event'
-            def cmNotificationSubscriptionNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent()
-        when: 'the method to publish cm notification subscription ncmp out event is called'
-            objectUnderTest.publishCmNotificationSubscriptionNcmpOutEvent("some-id",
-                "some-event", cmNotificationSubscriptionNcmpOutEvent, true)
-        then: 'the parameters is delegated to the correct method once'
-            1 * mockCmNotificationSubscriptionNcmpOutEventProducer.publishCmNotificationSubscriptionNcmpOutEvent(
-                "some-id", "some-event", cmNotificationSubscriptionNcmpOutEvent, true)
-    }
-
-    def 'Publish cm notification subscription dmi in event'() {
-        given: 'a dmi in event'
-            def cmNotificationSubscriptionDmiInEvent = new CmNotificationSubscriptionDmiInEvent()
-        when: 'the method to publish cm notification subscription ncmp out event is called'
-            objectUnderTest.publishCmNotificationSubscriptionDmiInEvent("some-id",
-                "some-dmi", "some-event", cmNotificationSubscriptionDmiInEvent)
-        then: 'the parameters is delegated to the correct method once'
-            1 * mockCmNotificationSubscriptionDmiInEventProducer.publishCmNotificationSubscriptionDmiInEvent("some-id",
-                "some-dmi", "some-event", cmNotificationSubscriptionDmiInEvent)
-    }
-}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandlerSpec.groovy
deleted file mode 100644 (file)
index bdc54bd..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription
-
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper
-import spock.lang.Specification
-
-class CmNotificationSubscriptionMappersHandlerSpec extends Specification{
-
-    def mockCmNotificationDmiInEventMapper = Mock(CmNotificationSubscriptionDmiInEventMapper)
-    def mockCmNotificationNcmpOutEventMapper = Mock(CmNotificationSubscriptionNcmpOutEventMapper)
-
-    def objectUnderTest = new CmNotificationSubscriptionMappersHandler(mockCmNotificationDmiInEventMapper,
-        mockCmNotificationNcmpOutEventMapper)
-
-    def 'Get cm notification subscription DMI in event'() {
-        given: 'a list of predicates'
-            def testListOfPredicates = []
-        when: 'method to create a cm notification subscription dmi in event is called with predicates'
-            objectUnderTest.toCmNotificationSubscriptionDmiInEvent(testListOfPredicates)
-        then: 'the parameters is delegated to the correct dmi in event mapper method'
-            1 * mockCmNotificationDmiInEventMapper.toCmNotificationSubscriptionDmiInEvent(testListOfPredicates)
-    }
-
-    def 'Get cm notification subscription ncmp out event'() {
-        given: 'a subscription details map'
-            def testSubscriptionDetailsMap = [:]
-        when: 'method to create cm notification subscription ncmp out event is called with the following parameters'
-            objectUnderTest.toCmNotificationSubscriptionNcmpOutEvent("test-id", testSubscriptionDetailsMap)
-        then: 'the parameters is delegated to the correct ncmp out event mapper method'
-            1 * mockCmNotificationNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEvent("test-id",
-            testSubscriptionDetailsMap)
-    }
-
-    def 'Get cm notification subscription ncmp out event for a rejected request'() {
-        given: 'a list of target filters'
-            def testRejectedTargetFilters = []
-        when: 'method to create cm notification subscription ncmp out event is called with the following parameters'
-            objectUnderTest.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(
-                "test-id", testRejectedTargetFilters)
-        then: 'the parameters is delegated to the correct ncmp out event mapper method'
-            1 * mockCmNotificationNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(
-                "test-id", testRejectedTargetFilters)
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy
deleted file mode 100644 (file)
index 77bbe7e..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.onap.cps.ncmp.api.impl.events.cmsubscription
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import io.cloudevents.CloudEvent
-import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer
-import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.Data
-import org.onap.cps.utils.JsonObjectMapper
-import spock.lang.Specification
-
-class CmNotificationSubscriptionNcmpOutEventProducerSpec extends Specification {
-
-    def mockEventsPublisher = Mock(EventsPublisher)
-    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
-    def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler)
-    def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler)
-
-    def objectUnderTest = new CmNotificationSubscriptionNcmpOutEventProducer(mockEventsPublisher, jsonObjectMapper,
-        mockCmNotificationSubscriptionMappersHandler, mockDmiCmNotificationSubscriptionCacheHandler)
-
-    def 'Create and #scenario Cm Notification Subscription NCMP out event'() {
-        given: 'a cm subscription response for the client'
-            def subscriptionId = 'test-subscription-id-2'
-            def eventType = 'subscriptionCreateResponse'
-            def cmNotificationSubscriptionNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent(data: new Data(subscriptionId: 'test-subscription-id-2', acceptedTargets: ['ch-1', 'ch-2']))
-        and: 'also we have target topic for publishing to client'
-            objectUnderTest.cmNotificationSubscriptionNcmpOutEventTopic = 'client-test-topic'
-        and: 'a deadline to an event'
-            objectUnderTest.cmNotificationSubscriptionDmiOutEventTimeoutInMs = 1000
-        when: 'the event is published'
-            objectUnderTest.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, eventType, cmNotificationSubscriptionNcmpOutEvent, eventPublishingTaskToBeScheduled)
-        then: 'we conditionally wait for a while'
-            Thread.sleep(delayInMs)
-        then: 'the event contains the required attributes'
-            1 * mockEventsPublisher.publishCloudEvent(_, _, _) >> {
-                args ->
-                    {
-                        assert args[0] == 'client-test-topic'
-                        assert args[1] == subscriptionId
-                        def cmNotificationSubscriptionNcmpOutEventAsCloudEvent = (args[2] as CloudEvent)
-                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.getExtension('correlationid') == subscriptionId
-                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.type == 'subscriptionCreateResponse'
-                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.source.toString() == 'NCMP'
-                        assert CloudEventMapper.toTargetEvent(cmNotificationSubscriptionNcmpOutEventAsCloudEvent, CmNotificationSubscriptionNcmpOutEvent) == cmNotificationSubscriptionNcmpOutEvent
-                    }
-            }
-        where: 'following scenarios are considered'
-            scenario                                          | delayInMs | eventPublishingTaskToBeScheduled
-            'publish event now'                               | 0         | false
-            'schedule and publish after the configured time ' | 1500      | true
-    }
-
-    def 'Schedule Cm Notification Subscription NCMP out event but later publish it on demand'() {
-        given: 'a cm subscription response for the client'
-            def subscriptionId = 'test-subscription-id-3'
-            def eventType = 'subscriptionCreateResponse'
-            def cmNotificationSubscriptionNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent(data: new Data(subscriptionId: 'test-subscription-id-3', acceptedTargets: ['ch-2', 'ch-3']))
-        and: 'also we have target topic for publishing to client'
-            objectUnderTest.cmNotificationSubscriptionNcmpOutEventTopic = 'client-test-topic'
-        and: 'a deadline to an event'
-            objectUnderTest.cmNotificationSubscriptionDmiOutEventTimeoutInMs = 1000
-        when: 'the event is scheduled to be published'
-            objectUnderTest.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, eventType, cmNotificationSubscriptionNcmpOutEvent, true)
-        then: 'we wait for 10ms and then we receive response from DMI'
-            Thread.sleep(10)
-        and: 'we receive response from DMI so we publish the message on demand'
-            objectUnderTest.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, eventType, cmNotificationSubscriptionNcmpOutEvent, false)
-        then: 'the event contains the required attributes'
-            1 * mockEventsPublisher.publishCloudEvent(_, _, _) >> {
-                args ->
-                    {
-                        assert args[0] == 'client-test-topic'
-                        assert args[1] == subscriptionId
-                        def cmNotificationSubscriptionNcmpOutEventAsCloudEvent = (args[2] as CloudEvent)
-                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.getExtension('correlationid') == subscriptionId
-                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.type == 'subscriptionCreateResponse'
-                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.source.toString() == 'NCMP'
-                        assert CloudEventMapper.toTargetEvent(cmNotificationSubscriptionNcmpOutEventAsCloudEvent, CmNotificationSubscriptionNcmpOutEvent) == cmNotificationSubscriptionNcmpOutEvent
-                    }
-            }
-        then: 'the cache handler is called once to remove accepted and rejected entries in cache'
-            1 * mockDmiCmNotificationSubscriptionCacheHandler.removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(subscriptionId)
-    }
-
-
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy
deleted file mode 100644 (file)
index f6bb24c..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.impl.events.cmsubscription.mapper
-
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType
-import spock.lang.Specification
-
-class CmNotificationSubscriptionNcmpOutEventMapperSpec extends Specification {
-
-    static Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap
-
-    def objectUnderTest = new CmNotificationSubscriptionNcmpOutEventMapper()
-
-    def setup() {
-        def dmiCmNotificationSubscriptionPredicateA = new DmiCmNotificationSubscriptionPredicate(['ch-A'] as Set, DatastoreType.PASSTHROUGH_RUNNING, ['/a'] as Set)
-        def dmiCmNotificationSubscriptionPredicateB = new DmiCmNotificationSubscriptionPredicate(['ch-B'] as Set, DatastoreType.PASSTHROUGH_OPERATIONAL, ['/b'] as Set)
-        def dmiCmNotificationSubscriptionPredicateC = new DmiCmNotificationSubscriptionPredicate(['ch-C'] as Set, DatastoreType.PASSTHROUGH_OPERATIONAL, ['/c'] as Set)
-        dmiCmNotificationSubscriptionDetailsMap = ['dmi-1': new DmiCmNotificationSubscriptionDetails([dmiCmNotificationSubscriptionPredicateA], CmNotificationSubscriptionStatus.PENDING),
-                                                   'dmi-2': new DmiCmNotificationSubscriptionDetails([dmiCmNotificationSubscriptionPredicateB], CmNotificationSubscriptionStatus.ACCEPTED),
-                                                   'dmi-3': new DmiCmNotificationSubscriptionDetails([dmiCmNotificationSubscriptionPredicateC], CmNotificationSubscriptionStatus.REJECTED)
-        ]
-    }
-
-    def 'Check for Cm Notification Subscription Outgoing event mapping'() {
-        when: 'we try to map the event to send it to client'
-            def result = objectUnderTest.toCmNotificationSubscriptionNcmpOutEvent('test-subscription', dmiCmNotificationSubscriptionDetailsMap)
-        then: 'event is mapped correctly for the subscription'
-            result.data.subscriptionId == 'test-subscription'
-        and: 'the cm handle ids are part of correct list'
-            result.data.pendingTargets == ['ch-A']
-            result.data.acceptedTargets == ['ch-B']
-            result.data.rejectedTargets == ['ch-C']
-    }
-
-    def 'Check for Cm Notification Rejected Subscription Outgoing event mapping'() {
-        when: 'we try to map the event to send it to client'
-            def result = objectUnderTest.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest('test-subscription', ['ch-1', 'ch-2'])
-        then: 'event is mapped correctly for the subscription id'
-            result.data.subscriptionId == 'test-subscription'
-        and: 'the cm handle ids are part of correct list'
-            result.data.withRejectedTargets(['ch-1', 'ch-2'])
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy
deleted file mode 100644 (file)
index 98b4ee2..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (c) 2024 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.impl.events.cmsubscription.service
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionDelta
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent
-import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent
-import org.onap.cps.ncmp.utils.TestUtils
-import org.onap.cps.utils.JsonObjectMapper
-import spock.lang.Specification
-
-class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{
-
-    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
-    def mockCmNotificationSubscriptionPersistenceService = Mock(CmNotificationSubscriptionPersistenceService);
-    def mockCmNotificationSubscriptionDelta = Mock(CmNotificationSubscriptionDelta);
-    def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler);
-    def mockCmNotificationSubscriptionEventsHandler = Mock(CmNotificationSubscriptionEventsHandler);
-    def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler);
-
-    def objectUnderTest = new CmNotificationSubscriptionHandlerServiceImpl(mockCmNotificationSubscriptionPersistenceService,
-        mockCmNotificationSubscriptionDelta, mockCmNotificationSubscriptionMappersHandler,
-        mockCmNotificationSubscriptionEventsHandler, mockDmiCmNotificationSubscriptionCacheHandler)
-
-    def testSubscriptionDetailsMap = ["dmi-1":new DmiCmNotificationSubscriptionDetails([], CmNotificationSubscriptionStatus.PENDING)]
-    def testListOfDeltaPredicates = []
-
-    def 'Consume valid and unique CmNotificationSubscriptionNcmpInEvent create message'() {
-        given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id'
-            def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
-            def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class)
-            mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true
-        and: 'the cache handler returns for relevant subscription id'
-            1 * mockDmiCmNotificationSubscriptionCacheHandler.get("test-id") >> testSubscriptionDetailsMap
-        and: 'the delta predicates is returned'
-            1 * mockCmNotificationSubscriptionDelta.getDelta(_) >> testListOfDeltaPredicates
-        and: 'the DMI in event mapper returns cm notification subscription event'
-            def testDmiInEvent = new CmNotificationSubscriptionDmiInEvent()
-            1 *  mockCmNotificationSubscriptionMappersHandler
-                .toCmNotificationSubscriptionDmiInEvent(testListOfDeltaPredicates) >> testDmiInEvent
-        when: 'the valid and unique event is consumed'
-            objectUnderTest.processSubscriptionCreateRequest(testEventConsumed)
-        then: 'the subscription cache handler is called once'
-            1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id',_)
-        and: 'the events handler method to publish DMI event is called correct number of times with the correct parameters'
-            testSubscriptionDetailsMap.size() * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(
-                "test-id", "dmi-1", "subscriptionCreateRequest", testDmiInEvent)
-        and: 'we schedule to send the response after configured time from the cache'
-            1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(
-                "test-id", "subscriptionCreateResponse", null, true)
-    }
-
-    def 'Consume valid and but non-unique CmNotificationSubscription create message'() {
-        given: 'a cmNotificationSubscriptionNcmp in event'
-            def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
-            def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class)
-            mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId('test-id') >> false
-        and: 'the NCMP out in event mapper returns an event for rejected request'
-            def testNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent()
-            1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(
-                "test-id",_) >> testNcmpOutEvent
-        when: 'the valid but non-unique event is consumed'
-            objectUnderTest.processSubscriptionCreateRequest(testEventConsumed)
-        then: 'the events handler method to publish DMI event is never called'
-            0 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(_,_,_,_)
-        and: 'the events handler method to publish NCMP out event is called once'
-            1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(
-                'test-id', 'subscriptionCreateResponse', testNcmpOutEvent, false)
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
deleted file mode 100644 (file)
index eb6c7a0..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation
- *  Modifications 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.impl.operations
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
-import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
-import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext
-import org.onap.cps.ncmp.api.models.DataOperationRequest
-import org.onap.cps.ncmp.api.models.CmResourceAddress
-import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent
-import org.onap.cps.ncmp.utils.TestUtils
-import org.onap.cps.utils.JsonObjectMapper
-import org.spockframework.spring.SpringBean
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.http.HttpStatus
-import org.springframework.http.ResponseEntity
-import org.springframework.test.context.ContextConfiguration
-import spock.lang.Shared
-
-import java.util.concurrent.TimeoutException
-
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
-
-@SpringBootTest
-@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, NcmpConfiguration.DmiProperties, DmiDataOperations])
-class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
-
-    @SpringBean
-    DmiServiceUrlBuilder dmiServiceUrlBuilder = Mock()
-    def dmiServiceBaseUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:"
-    def NO_TOPIC = null
-    def NO_REQUEST_ID = null
-    def NO_AUTH_HEADER = null
-    @Shared
-    def OPTIONS_PARAM = '(a=1,b=2)'
-
-    @SpringBean
-    JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
-
-    @Autowired
-    DmiDataOperations objectUnderTest
-
-    @SpringBean
-    EventsPublisher eventsPublisher = Stub()
-
-    def 'call get resource data for #expectedDatastoreInUrl from DMI without topic #scenario.'() {
-        given: 'a cm handle for #cmHandleId'
-            mockYangModelCmHandleRetrieval(dmiProperties)
-        and: 'a positive response from DMI service when it is called with the expected parameters'
-            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
-            def expectedUrl = dmiServiceBaseUrl + "${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}"
-            mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi
-            dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
-        when: 'get resource data is invoked'
-            def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, cmHandleId, resourceIdentifier)
-            def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
-        then: 'the result is the response from the DMI service'
-            assert result == responseFromDmi
-        where: 'the following parameters are used'
-            scenario                               | dmiProperties               | dataStore               | options       || expectedJson                                                 | expectedDatastoreInUrl    | expectedOptionsInUrl
-            'without properties'                   | []                          | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-operational' | '&options=(a=1,b=2)'
-            'with properties'                      | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '&options=(a=1,b=2)'
-            'null options'                         | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null          || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
-            'empty options'                        | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | ''            || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
-            'datastore running without properties' | []                          | PASSTHROUGH_RUNNING     | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-running'     | '&options=(a=1,b=2)'
-            'datastore running with properties'    | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING     | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running'     | '&options=(a=1,b=2)'
-    }
-
-    def 'Execute (async) data operation from DMI service.'() {
-        given: 'collection of yang model cm Handles and data operation request'
-            mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty])
-            def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
-            def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class)
-            dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId]
-        and: 'a positive response from DMI service when it is called with valid request parameters'
-            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED)
-            def expectedDmiBatchResourceDataUrl = "ncmp/v1/data/topic=my-topic-name"
-            def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","cmHandleProperties":{"prop1":"val1"}}]}]}'
-            mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ.operationName, NO_AUTH_HEADER) >> responseFromDmi
-            dmiServiceUrlBuilder.getDataOperationRequestUrl(_, _) >> expectedDmiBatchResourceDataUrl
-        when: 'get resource data for group of cm handles are invoked'
-            objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
-        then: 'the post operation was called and ncmp generated dmi request body json args'
-            1 * mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER)
-    }
-
-    def 'Execute (async) data operation from DMI service for #scenario.'() {
-        given: 'data operation request body and dmi resource url'
-            def dmiDataOperation = DmiDataOperation.builder().operationId('some-operation-id').build()
-            dmiDataOperation.getCmHandles().add(CmHandle.builder().id('some-cm-handle-id').build())
-            def dmiDataOperationResourceDataUrl = "http://dmi-service-name:dmi-port/dmi/v1/data?topic=my-topic-name&requestId=some-request-id"
-            def actualDataOperationCloudEvent = null
-        when: 'exception occurs after sending request to dmi service'
-            objectUnderTest.handleTaskCompletionException(new Throwable(exception), dmiDataOperationResourceDataUrl, List.of(dmiDataOperation))
-        then: 'a cloud event is published'
-            eventsPublisher.publishCloudEvent('my-topic-name', 'some-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] }
-        and: 'the event contains the expected error details'
-            def eventDataValue = extractDataValue(actualDataOperationCloudEvent)
-            assert eventDataValue.operationId == dmiDataOperation.operationId
-            assert eventDataValue.ids == dmiDataOperation.cmHandles.id
-            assert eventDataValue.statusCode == responseCode.code
-            assert eventDataValue.statusMessage == responseCode.message
-        where: 'the following exceptions are occurred'
-            scenario                        | exception                                                                                                || responseCode
-            'http client request exception' | new HttpClientRequestException('error-message', 'error-details', HttpStatus.SERVICE_UNAVAILABLE.value()) || UNABLE_TO_READ_RESOURCE_DATA
-            'timeout exception'             | new TimeoutException()                                                                                   || DMI_SERVICE_NOT_RESPONDING
-    }
-
-    def 'call get all resource data.'() {
-        given: 'the system returns a cm handle with a sample property'
-            mockYangModelCmHandleRetrieval([yangModelCmHandleProperty])
-        and: 'a positive response from DMI service when it is called with the expected parameters'
-            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
-            def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/"
-            mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}', READ, null) >> responseFromDmi
-            dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
-        when: 'get resource data is invoked'
-            def result = objectUnderTest.getResourceDataFromDmi( PASSTHROUGH_OPERATIONAL.datastoreName, cmHandleId, NO_REQUEST_ID)
-        then: 'the result is the response from the DMI service'
-            assert result == responseFromDmi
-    }
-
-    def 'Write data for pass-through:running datastore in DMI.'() {
-        given: 'a cm handle for #cmHandleId'
-            mockYangModelCmHandleRetrieval([yangModelCmHandleProperty])
-        and: 'a positive response from DMI service when it is called with the expected parameters'
-            def expectedUrl = dmiServiceBaseUrl + "passthrough-running?resourceIdentifier=${resourceIdentifier}"
-            def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"}}'
-            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
-            dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
-            mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi
-        when: 'write resource method is invoked'
-            def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER)
-        then: 'the result is the response from the DMI service'
-            assert result == responseFromDmi
-        where: 'the following operation is performed'
-            operation || expectedOperationInUrl
-            CREATE    || 'create'
-            UPDATE    || 'update'
-    }
-
-    def extractDataValue(actualDataOperationCloudEvent) {
-        return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent.class).data.responses[0]
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy
deleted file mode 100644 (file)
index 9f84fb0..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * ============LICENSE_START========================================================
- * Copyright (c) 2023 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.impl.utils
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-
-import org.onap.cps.spi.model.DataNodeBuilder
-import spock.lang.Specification
-
-class DataNodeBaseSpec extends Specification {
-
-    def leaves1 = [status:'PENDING', cmHandleId:'CMHandle3', details:'Subscription forwarded to dmi plugin'] as Map
-    def dataNode1 = createDataNodeWithLeaves(leaves1)
-
-    def leaves2 = [status:'ACCEPTED', cmHandleId:'CMHandle2', details:''] as Map
-    def dataNode2 = createDataNodeWithLeaves(leaves2)
-
-    def leaves3 = [status:'REJECTED', cmHandleId:'CMHandle1', details:'Cm handle does not exist'] as Map
-    def dataNode3 = createDataNodeWithLeaves(leaves3)
-
-    def leaves4 = [datastore:'passthrough-running'] as Map
-    def dataNode4 = createDataNodeWithLeavesAndChildDataNodes(leaves4, [dataNode1, dataNode2, dataNode3])
-
-    static def createDataNodeWithLeaves(leaves) {
-        return new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME)
-            .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription')
-            .withLeaves(leaves).build()
-    }
-
-    static def createDataNodeWithLeavesAndChildDataNodes(leaves, dataNodes) {
-        return new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME)
-            .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription')
-            .withLeaves(leaves).withChildDataNodes(dataNodes)
-            .build()
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy
deleted file mode 100644 (file)
index 05e5f58..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * ============LICENSE_START========================================================
- * Copyright (c) 2023 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.impl.utils
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-
-import org.onap.cps.spi.model.DataNodeBuilder
-
-class DataNodeHelperSpec extends DataNodeBaseSpec {
-
-    def 'Get data node leaves as expected from a nested data node.'() {
-        given: 'a nested data node'
-            def dataNode = new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME)
-                .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription')
-                .withLeaves([clientID:'SCO-9989752', isTagged:false, subscriptionName:'cm-subscription-001'])
-                .withChildDataNodes([dataNode4]).build()
-        when: 'the nested data node is flatten and retrieves the leaves '
-            def result = DataNodeHelper.getDataNodeLeaves([dataNode])
-        then: 'the result list size is 5'
-            result.size() == 5
-        and: 'all the leaves result list are equal to given leaves of data nodes'
-            result[0] == [clientID:'SCO-9989752', isTagged:false, subscriptionName:'cm-subscription-001']
-            result[1] == [datastore:'passthrough-running']
-            result[2] == [status:'PENDING', cmHandleId:'CMHandle3', details:'Subscription forwarded to dmi plugin']
-            result[3] == [status:'ACCEPTED', cmHandleId:'CMHandle2', details:'']
-            result[4] == [status:'REJECTED', cmHandleId:'CMHandle1', details:'Cm handle does not exist']
-    }
-
-    def 'Get cm handle id to status as expected from a nested data node.'() {
-        given: 'a nested data node'
-            def dataNode = new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME)
-                .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription')
-                .withLeaves([clientID:'SCO-9989752', isTagged:false, subscriptionName:'cm-subscription-001'])
-                .withChildDataNodes([dataNode4]).build()
-        and: 'the nested data node is flatten and retrieves the leaves '
-            def leaves = DataNodeHelper.getDataNodeLeaves([dataNode])
-        when:'cm handle id to status is retrieved'
-            def result = DataNodeHelper.cmHandleIdToStatusAndDetailsAsMap(leaves)
-        then: 'the result list size is 3'
-            result.size() == 3
-        and: 'the result contains expected values'
-            result == [
-                CMHandle3: [details:'Subscription forwarded to dmi plugin',status:'PENDING'] as Map,
-                CMHandle2: [details:'',status:'ACCEPTED'] as Map,
-                CMHandle1: [details:'Cm handle does not exist',status:'REJECTED'] as Map
-            ] as Map
-
-    }
-
-    def 'Get cm handle id to status map as expected from a nested data node.'() {
-        given: 'a nested data node'
-            def dataNode = new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME)
-                .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription')
-                .withLeaves([clientID:'SCO-9989752', isTagged:false, subscriptionName:'cm-subscription-001'])
-                .withChildDataNodes([dataNode4]).build()
-        when:'cm handle id to status is being extracted'
-            def result = DataNodeHelper.cmHandleIdToStatusAndDetailsAsMapFromDataNode([dataNode]);
-        then: 'the result list size is 3'
-            result.size() == 3
-        and: 'the result contains expected values'
-            result == [
-                CMHandle3: [details:'Subscription forwarded to dmi plugin',status:'PENDING'] as Map,
-                CMHandle2: [details:'',status:'ACCEPTED'] as Map,
-                CMHandle1: [details:'Cm handle does not exist',status:'REJECTED'] as Map
-            ] as Map
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy
deleted file mode 100644 (file)
index fbf2c3d..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 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.impl.utils
-
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-
-import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService
-import org.onap.cps.spi.utils.CpsValidator
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import spock.lang.Specification
-
-class DmiServiceUrlBuilderSpec extends Specification {
-
-    static YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName',
-        'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id'),'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier')
-
-    NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties()
-
-    def mockCpsValidator = Mock(CpsValidator)
-
-    def objectUnderTest = new DmiServiceUrlBuilder(dmiProperties, mockCpsValidator)
-
-    def setup() {
-        dmiProperties.dmiBasePath = 'dmi'
-    }
-
-    def 'Create the dmi service url with #scenario.'() {
-        given: 'uri variables'
-            def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle')
-        and: 'query params'
-            def uriQueries = objectUnderTest.populateQueryParams(resourceId, 'optionsParamInQuery', topic)
-        when: 'a dmi datastore service url is generated'
-            def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars)
-        then: 'service url is generated as expected'
-            assert dmiServiceUrl == expectedDmiServiceUrl
-        where: 'the following parameters are used'
-            scenario                       | topic               | resourceId   || expectedDmiServiceUrl
-            'With valid resourceId'        | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
-            'With Empty resourceId'        | 'topicParamInQuery' | ''           || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?options=optionsParamInQuery&topic=topicParamInQuery'
-            'With Empty dmi base path'     | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
-            'With Empty topicParamInQuery' | ''                  | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery'
-    }
-
-    def 'Populate dmi data store url #scenario.'() {
-        given: 'uri variables are created'
-            dmiProperties.dmiBasePath = dmiBasePath
-            def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle')
-        and: 'null query params'
-            def uriQueries = objectUnderTest.populateQueryParams(null, null, null)
-        when: 'a dmi datastore service url is generated'
-            def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars)
-        then: 'the created dmi service url matches the expected'
-            assert dmiServiceUrl == expectedDmiServiceUrl
-        where: 'the following parameters are used'
-            scenario               | decription                                | dmiBasePath || expectedDmiServiceUrl
-            'with base path  / '   | 'Invalid base path as it starts with /'   | '/dmi'      || 'dmiServiceName//dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
-            'without base path / ' | 'Valid path as it does not starts with /' | 'dmi'       || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
-    }
-
-    def 'Bath request Url creation.'() {
-        given: 'the required path parameters'
-            def batchRequestUriVariables = [dmiServiceName: 'some-service', dmiBasePath: 'testBase', cmHandleId: '123']
-        and: 'the relevant query parameters'
-            def batchRequestQueryParams = objectUnderTest.getDataOperationRequestQueryParams('some topic', 'some id')
-        when: 'a URL is created'
-            def result = objectUnderTest.getDataOperationRequestUrl(batchRequestQueryParams, batchRequestUriVariables)
-        then: 'it is formed correctly'
-            assert result.toString() == 'some-service/testBase/v1/data?topic=some topic&requestId=some id'
-    }
-
-    def 'Populate batch uri variables.'() {
-        expect: 'Populate batch uri variables returns a map with given service name and base path from setup'
-            assert objectUnderTest.populateDataOperationRequestUriVariables('some service')  == [dmiServiceName: 'some service', dmiBasePath: 'dmi' ]
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/context/CpsApplicationContextSpec.groovy
deleted file mode 100644 (file)
index b7fa449..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.onap.cps.ncmp.api.impl.utils.context
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.utils.JsonObjectMapper
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.test.context.ContextConfiguration
-import spock.lang.Specification;
-
-@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
-@ContextConfiguration(classes = [CpsApplicationContext.class])
-class CpsApplicationContextSpec extends Specification {
-
-    def 'Verify if cps application context contains a requested bean.'() {
-        when: 'cps bean is requested from application context'
-            def jsonObjectMapper = CpsApplicationContext.getCpsBean(JsonObjectMapper.class)
-        then: 'requested bean of JsonObjectMapper is not null'
-            assert jsonObjectMapper != null
-    }
-}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models
+package org.onap.cps.ncmp.api.inventory.models
+
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status
+import spock.lang.Specification
+
+import java.util.stream.Collectors
 
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
 
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
-import spock.lang.Specification
-import java.util.stream.Collectors
-
 class CmHandleRegistrationResponseSpec extends Specification {
 
     def 'Successful cm-handle Registration Response'() {
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory
+package org.onap.cps.ncmp.api.inventory.models
 
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
+
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
 import spock.lang.Specification
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory
+package org.onap.cps.ncmp.api.inventory.models
 
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory
 import spock.lang.Specification
+
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
 
-import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
-import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
+import static org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores
+import static org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational
 import static org.onap.cps.ncmp.utils.TestUtils.getResourceFileContent
 import static org.springframework.util.StringUtils.trimAllWhitespace
 
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/CpsApplicationContextSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/CpsApplicationContextSpec.groovy
new file mode 100644 (file)
index 0000000..215ea6c
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * ============LICENSE_START========================================================
+ * Copyright (c) 2023-2024 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.config
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.utils.JsonObjectMapper
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification;
+
+@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
+@ContextConfiguration(classes = [CpsApplicationContext.class])
+class CpsApplicationContextSpec extends Specification {
+
+    def 'Verify if cps application context contains a requested bean.'() {
+        when: 'cps bean is requested from application context'
+            def jsonObjectMapper = CpsApplicationContext.getCpsBean(JsonObjectMapper.class)
+        then: 'requested bean of JsonObjectMapper is not null'
+            assert jsonObjectMapper != null
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy
new file mode 100644 (file)
index 0000000..23f5edd
--- /dev/null
@@ -0,0 +1,73 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023-2024 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.config
+
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [DmiHttpClientConfig])
+@EnableConfigurationProperties
+class DmiHttpClientConfigSpec extends Specification {
+
+    @Autowired
+    DmiHttpClientConfig dmiHttpClientConfig
+
+    def 'Test http client configuration properties of data with custom and default values'() {
+        expect: 'properties are populated correctly for data services'
+            with(dmiHttpClientConfig.dataServices) {
+                assert maximumInMemorySizeInMegabytes == 1
+                assert maximumConnectionsTotal == 2
+                assert pendingAcquireMaxCount == 3
+                assert connectionTimeoutInSeconds == 4
+                assert readTimeoutInSeconds == 5
+                assert writeTimeoutInSeconds == 6
+            }
+    }
+
+    def 'Test http client configuration properties of model with custom and default values'() {
+        expect: 'properties are populated correctly for model services'
+            with(dmiHttpClientConfig.modelServices) {
+                assert maximumInMemorySizeInMegabytes == 11
+                assert maximumConnectionsTotal == 12
+                assert pendingAcquireMaxCount == 13
+                assert connectionTimeoutInSeconds == 14
+                assert readTimeoutInSeconds == 15
+                assert writeTimeoutInSeconds == 16
+            }
+    }
+
+    def 'Test http client configuration properties of health with default values'() {
+        expect: 'properties are populated correctly for health check services'
+            with(dmiHttpClientConfig.healthCheckServices) {
+                assert maximumInMemorySizeInMegabytes == 21
+                assert maximumConnectionsTotal == 22
+                assert pendingAcquireMaxCount == 23
+                assert connectionTimeoutInSeconds == 24
+                assert readTimeoutInSeconds == 25
+                assert writeTimeoutInSeconds == 26
+            }
+    }
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.config.kafka;
+package org.onap.cps.ncmp.config
 
 import io.cloudevents.CloudEvent
 import io.cloudevents.kafka.CloudEventDeserializer
@@ -31,12 +31,14 @@ import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.kafka.core.KafkaTemplate
 import org.springframework.kafka.support.serializer.JsonDeserializer
 import org.springframework.kafka.support.serializer.JsonSerializer
+import org.springframework.test.context.TestPropertySource
 import spock.lang.Shared
 import spock.lang.Specification
 
 @SpringBootTest(classes = [KafkaProperties, KafkaConfig])
 @EnableSharedInjection
 @EnableConfigurationProperties
+@TestPropertySource(properties = ["cps.tracing.enabled=true"])
 class KafkaConfigSpec extends Specification {
 
     @Shared
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryConfigSpec.groovy
new file mode 100644 (file)
index 0000000..cbff731
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 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.config
+
+import io.micrometer.observation.ObservationPredicate
+import io.micrometer.observation.ObservationRegistry
+import io.micrometer.observation.ObservationRegistry.ObservationConfig
+import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
+import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter
+import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.server.observation.ServerRequestObservationContext
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.util.AntPathMatcher
+import spock.lang.Specification
+
+@SpringBootTest(classes = [OpenTelemetryConfig])
+class OpenTelemetryConfigSpec extends Specification {
+
+    def objectUnderTest
+
+    @Value('${cps.tracing.exporter.endpoint}')
+    def tracingExporterEndpointUrl
+
+    @Value('${cps.tracing.sampler.jaeger_remote.endpoint}')
+    def jaegerRemoteSamplerUrl
+
+    def setup() {
+        objectUnderTest = new OpenTelemetryConfig(
+                serviceId: 'sample-app',
+                tracingExporterEndpointUrl: tracingExporterEndpointUrl,
+                jaegerRemoteSamplerUrl: jaegerRemoteSamplerUrl,
+                excludedObservationNames: ['excluded-task-name'])
+    }
+
+    def 'OTLP exporter creation with Grpc protocol'() {
+        when: 'an OTLP exporter is created'
+            def result = objectUnderTest.createOtlpExporterGrpc()
+        then: 'expected an instance of OtlpGrpcSpanExporter'
+            assert result instanceof OtlpGrpcSpanExporter
+    }
+
+    def 'OTLP exporter creation with HTTP protocol'() {
+        when: 'an OTLP exporter is created'
+            def result = objectUnderTest.createOtlpExporterHttp()
+        then: 'an OTLP Exporter is created'
+            assert result instanceof OtlpHttpSpanExporter
+        and: 'the endpoint is correctly set'
+            assert result.builder.endpoint == 'http://exporter-test-url'
+    }
+
+    def 'Jaeger Remote Sampler Creation'() {
+        when: 'a Jaeger remote sampler is created'
+            def result = objectUnderTest.createJaegerRemoteSampler()
+        then: 'a Jaeger remote sampler is created'
+            assert result instanceof JaegerRemoteSampler
+        and: 'the sampler type is correct'
+            assert result.delegate.type == 'remoteSampling'
+        and: 'the sampler endpoint is correctly set'
+            assert result.delegate.url.toString().startsWith('http://jaeger-remote-test-url')
+    }
+
+    def 'Skipping actuator endpoints'() {
+        given: 'a mocked observation registry and config'
+            def observationRegistry = Mock(ObservationRegistry.class)
+            def observationConfig = Mock(ObservationConfig.class)
+            observationRegistry.observationConfig() >> observationConfig
+        when: 'an observation registry customizer is created and applied'
+            def result = objectUnderTest.skipActuatorEndpointsFromObservation()
+            result.customize(observationRegistry)
+        then: 'the observation predicate is set correctly'
+            1 * observationConfig.observationPredicate(_) >> { ObservationPredicate observationPredicate ->
+                    def mockedHttpServletRequest = new MockHttpServletRequest(_ as String, requestUrl)
+                    def serverRequestObservationContext = new ServerRequestObservationContext(mockedHttpServletRequest, null)
+                and: 'expected predicate for endpoint'
+                    assert observationPredicate.test('some-name', serverRequestObservationContext) == expectedPredicate
+            }
+        where: 'the following parameters are used'
+            scenario         | requestUrl  || expectedPredicate
+            'an actuator'    | '/actuator' || false
+            'a non actuator' | '/some-api' || true
+    }
+
+    def 'Observation predicate is configured to filter out excluded tasks by name'() {
+        when: 'a path matcher and observation predicate'
+            def observationPredicate = objectUnderTest.observationPredicate(new AntPathMatcher('/'))
+        then: 'a task name is provided'
+            assert observationPredicate.test(taskName, null) == expectedPredicate
+        where: 'the following parameters are used'
+            taskName                 || expectedPredicate
+            'excluded-task-name'     || false
+            'non-excluded-task-name' || true
+    }
+}
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation.
+ *  Copyright (C) 2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * SPDX-License-Identifier: Apache-2.0
  * ============LICENSE_END=========================================================
  */
-package org.onap.cps.ncmp.api.impl.config
 
-import java.time.Duration
+package org.onap.cps.ncmp.config
+
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.context.properties.EnableConfigurationProperties
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.test.context.ContextConfiguration
-import org.springframework.test.context.TestPropertySource
-import org.springframework.test.context.support.AnnotationConfigContextLoader
 import spock.lang.Specification
 
 @SpringBootTest
-@ContextConfiguration(classes = [HttpClientConfiguration])
-@EnableConfigurationProperties(HttpClientConfiguration.class)
-@TestPropertySource(properties = ["ncmp.dmi.httpclient.connectionTimeoutInSeconds=1", "ncmp.dmi.httpclient.maximumConnectionsTotal=200"])
-class HttpClientConfigurationSpec extends Specification {
+@ContextConfiguration(classes = [PolicyExecutorHttpClientConfig])
+@EnableConfigurationProperties
+class PolicyExecutorHttpClientConfigSpec extends Specification {
 
     @Autowired
-    private HttpClientConfiguration httpClientConfiguration
+    PolicyExecutorHttpClientConfig policyExecutorHttpClientConfig
 
-    def 'Test HttpClientConfiguration properties with custom and default values'() {
-        expect: 'custom property values'
-        assert httpClientConfiguration.getConnectionTimeoutInSeconds() == Duration.ofSeconds(1)
-        assert httpClientConfiguration.getMaximumConnectionsTotal() == 200
-        and: 'default property values'
-        assert httpClientConfiguration.getMaximumConnectionsPerRoute() == 50
-        assert httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds() == Duration.ofSeconds(5)
+    def 'Http client configuration properties for policy executor http client.'() {
+        expect: 'properties are populated correctly for all services'
+            with(policyExecutorHttpClientConfig.allServices) {
+                assert maximumInMemorySizeInMegabytes == 31
+                assert maximumConnectionsTotal == 32
+                assert pendingAcquireMaxCount == 33
+                assert connectionTimeoutInSeconds == 34
+                assert readTimeoutInSeconds == 35
+                assert writeTimeoutInSeconds == 36
+            }
     }
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.config.embeddedcache
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.cache
 
 import com.hazelcast.core.Hazelcast
 import com.hazelcast.map.IMap
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType
+import org.onap.cps.ncmp.api.data.models.DatastoreType
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import spock.lang.Specification
 
-@SpringBootTest(classes = [CmNotificationSubscriptionCacheConfig])
-class CmNotificationSubscriptionCacheConfigSpec extends Specification {
+@SpringBootTest(classes = [CmSubscriptionConfig])
+class CmSubscriptionConfigSpec extends Specification {
 
     @Autowired
-    IMap<String, Map<String, DmiCmNotificationSubscriptionDetails>> cmNotificationSubscriptionCache;
+    IMap<String, Map<String, DmiCmSubscriptionDetails>> cmNotificationSubscriptionCache;
 
     def 'Embedded (hazelcast) cache for Cm Notification Subscription Cache.'() {
         expect: 'system is able to create an instance of the Cm Notification Subscription Cache'
@@ -49,15 +49,15 @@ class CmNotificationSubscriptionCacheConfigSpec extends Specification {
         given: 'a cm subscription properties'
             def subscriptionId = 'sub123'
             def dmiPluginName = 'dummydmi'
-            def cmSubscriptionPredicate = new DmiCmNotificationSubscriptionPredicate(['cmhandle1', 'cmhandle2'].toSet(), DatastoreType.PASSTHROUGH_RUNNING, ['/a/b/c'].toSet())
-            def cmSubscriptionCacheObject = new DmiCmNotificationSubscriptionDetails([cmSubscriptionPredicate], CmNotificationSubscriptionStatus.PENDING)
+            def cmSubscriptionPredicates = new DmiCmSubscriptionPredicate(['cmhandle1', 'cmhandle2'].toSet(), DatastoreType.PASSTHROUGH_RUNNING, ['/a/b/c'].toSet())
+            def cmSubscriptionCacheObject = new DmiCmSubscriptionDetails([cmSubscriptionPredicates], CmSubscriptionStatus.PENDING)
         when: 'the cache is populated'
             cmNotificationSubscriptionCache.put(subscriptionId, [(dmiPluginName): cmSubscriptionCacheObject])
         then: 'the values are present in memory'
             assert cmNotificationSubscriptionCache.get(subscriptionId) != null
         and: 'properties match'
             assert dmiPluginName == cmNotificationSubscriptionCache.get(subscriptionId).keySet()[0]
-            assert cmSubscriptionCacheObject.cmNotificationSubscriptionStatus == cmNotificationSubscriptionCache.get(subscriptionId).values().cmNotificationSubscriptionStatus[0]
-            assert cmSubscriptionCacheObject.dmiCmNotificationSubscriptionPredicates[0].targetCmHandleIds == cmNotificationSubscriptionCache.get(subscriptionId).values().dmiCmNotificationSubscriptionPredicates[0].targetCmHandleIds[0]
+            assert cmSubscriptionCacheObject.cmSubscriptionStatus == cmNotificationSubscriptionCache.get(subscriptionId).values().cmSubscriptionStatus[0]
+            assert cmSubscriptionCacheObject.dmiCmSubscriptionPredicates[0].targetCmHandleIds == cmNotificationSubscriptionCache.get(subscriptionId).values().dmiCmSubscriptionPredicates[0].targetCmHandleIds[0]
     }
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.cache
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
 import io.cloudevents.core.builder.CloudEventBuilder
 import org.apache.kafka.clients.consumer.ConsumerRecord
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.NcmpInEvent
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
+import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent
 
 @SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
-class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec {
+class DmiCacheHandlerSpec extends MessagingBaseSpec {
 
     @Autowired
     JsonObjectMapper jsonObjectMapper
@@ -49,12 +49,12 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec {
     @SpringBean
     InventoryPersistence mockInventoryPersistence = Mock(InventoryPersistence)
     @SpringBean
-    CmNotificationSubscriptionPersistenceService mockCmNotificationSubscriptionPersistenceService = Mock(CmNotificationSubscriptionPersistenceService)
+    CmSubscriptionPersistenceService mockCmSubscriptionPersistenceService = Mock(CmSubscriptionPersistenceService)
 
     def testCache = [:]
-    def objectUnderTest = new DmiCmNotificationSubscriptionCacheHandler(mockCmNotificationSubscriptionPersistenceService, testCache, mockInventoryPersistence)
+    def objectUnderTest = new DmiCacheHandler(mockCmSubscriptionPersistenceService, testCache, mockInventoryPersistence)
 
-    CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent
+    NcmpInEvent ncmpInEvent
     def yangModelCmHandle1 = new YangModelCmHandle(id:'ch1',dmiServiceName:'dmi-1')
     def yangModelCmHandle2 = new YangModelCmHandle(id:'ch2',dmiServiceName:'dmi-2')
     def yangModelCmHandle3 = new YangModelCmHandle(id:'ch3',dmiServiceName:'dmi-1')
@@ -65,17 +65,34 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec {
         initialiseMockInventoryPersistenceResponses()
     }
 
-    def 'Load CM subscription event to cache'() {
-        given: 'a valid subscription event with Id'
-            def subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId()
+    def 'Load CM subscription event to cache with predicates'() {
+        given: 'a subscription event with id'
+            def subscriptionId = ncmpInEvent.getData().getSubscriptionId()
         and: 'list of predicates'
-            def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates()
-        when: 'a valid event object loaded in cache'
+            def predicates = ncmpInEvent.getData().getPredicates()
+        when: 'subscription is loaded to cache with predicates'
             objectUnderTest.add(subscriptionId, predicates)
-        then: 'the cache contains the correct entry with #subscriptionId subscription ID'
+        then: 'the number of entries in cache is correct'
+            assert testCache.size() == 1
+        and: 'the cache contains the correct entries'
             assert testCache.containsKey(subscriptionId)
     }
 
+    def 'Load CM subscription event to cache with dmi subscription details per dmi'() {
+        given: 'a subscription event with id'
+            def subscriptionId = ncmpInEvent.getData().getSubscriptionId()
+        and: 'dmi subscription details per dmi'
+            def dmiSubscriptionsPerDmi = [:]
+        when: 'subscription is loaded to cache with dmi subscription details per dmi'
+            objectUnderTest.add(subscriptionId, dmiSubscriptionsPerDmi)
+        then: 'the number of entries in cache is correct'
+            assert testCache.size() == 1
+        and: 'the cache contains the correct entries'
+            assert testCache.containsKey(subscriptionId)
+        and: 'the entry for the subscription ID matches the provided DMI subscription details'
+            assert testCache.get(subscriptionId) == dmiSubscriptionsPerDmi
+    }
+
     def 'Get cache entry via subscription id'() {
         given: 'the cache contains value for some-id'
             testCache.put('some-id',[:])
@@ -89,19 +106,19 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec {
         given: 'a map as the value for cache entry for some-id'
             def testMap = [:]
             testMap.put("dmi-1",
-                new DmiCmNotificationSubscriptionDetails([],CmNotificationSubscriptionStatus.ACCEPTED))
+                new DmiCmSubscriptionDetails([],CmSubscriptionStatus.ACCEPTED))
             testMap.put("dmi-2",
-                new DmiCmNotificationSubscriptionDetails([],CmNotificationSubscriptionStatus.REJECTED))
+                new DmiCmSubscriptionDetails([],CmSubscriptionStatus.REJECTED))
             testMap.put("dmi-3",
-                new DmiCmNotificationSubscriptionDetails([],CmNotificationSubscriptionStatus.PENDING))
+                new DmiCmSubscriptionDetails([],CmSubscriptionStatus.PENDING))
             testCache.put("test-id", testMap)
             assert testCache.get("test-id").size() == 3
         when: 'the method to remove accepted and rejected entries for test-id is called'
-            objectUnderTest.removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries("test-id")
+            objectUnderTest.removeAcceptedAndRejectedDmiSubscriptionEntries("test-id")
         then: 'all entries with status accepted/rejected are no longer present for test-id'
             testCache.get("test-id").each { key, testResultMap ->
-                assert testResultMap.cmNotificationSubscriptionStatus != CmNotificationSubscriptionStatus.ACCEPTED
-                    || testResultMap.cmNotificationSubscriptionStatus != CmNotificationSubscriptionStatus.REJECTED
+                assert testResultMap.cmSubscriptionStatus != CmSubscriptionStatus.ACCEPTED
+                    || testResultMap.cmSubscriptionStatus != CmSubscriptionStatus.REJECTED
             }
         and: 'the size of the map for cache entry test-id is as expected'
             assert testCache.get("test-id").size() == 1
@@ -109,9 +126,9 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec {
 
     def 'Create map for DMI cm notification subscription per DMI service name'() {
         given: 'list of predicates from the create subscription event'
-            def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates()
+            def predicates = ncmpInEvent.getData().getPredicates()
         when: 'method to create map of DMI cm notification subscription per DMI service name is called'
-            def result = objectUnderTest.createDmiCmNotificationSubscriptionsPerDmi(predicates)
+            def result = objectUnderTest.createDmiSubscriptionsPerDmi(predicates)
         then: 'the result size of resulting map is correct to the number of DMIs'
             assert result.size() == 2
         and: 'the cache objects per DMI exists'
@@ -120,28 +137,28 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec {
             assert resultMapForDmi1 != null
             assert resultMapForDmi2 != null
         and: 'the size of predicates in each object is correct'
-            assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates.size() == 2
-            assert resultMapForDmi2.dmiCmNotificationSubscriptionPredicates.size() == 2
+            assert resultMapForDmi1.dmiCmSubscriptionPredicates.size() == 2
+            assert resultMapForDmi2.dmiCmSubscriptionPredicates.size() == 2
         and: 'the subscription status in each object is correct'
-            assert resultMapForDmi1.cmNotificationSubscriptionStatus.toString() == 'PENDING'
-            assert resultMapForDmi2.cmNotificationSubscriptionStatus.toString() == 'PENDING'
+            assert resultMapForDmi1.cmSubscriptionStatus.toString() == 'PENDING'
+            assert resultMapForDmi2.cmSubscriptionStatus.toString() == 'PENDING'
         and: 'the target cmHandles for each predicate is correct'
-            assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[0].targetCmHandleIds == ['ch1'].toSet()
-            assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[1].targetCmHandleIds == ['ch3'].toSet()
+            assert resultMapForDmi1.dmiCmSubscriptionPredicates[0].targetCmHandleIds == ['ch1'].toSet()
+            assert resultMapForDmi1.dmiCmSubscriptionPredicates[1].targetCmHandleIds == ['ch3'].toSet()
 
-            assert resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[0].targetCmHandleIds == ['ch2'].toSet()
-            assert resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].targetCmHandleIds == ['ch4'].toSet()
+            assert resultMapForDmi2.dmiCmSubscriptionPredicates[0].targetCmHandleIds == ['ch2'].toSet()
+            assert resultMapForDmi2.dmiCmSubscriptionPredicates[1].targetCmHandleIds == ['ch4'].toSet()
         and: 'the list of xpath for each is correct'
-            assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[0].xpaths
-                    && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[0].xpaths == ['/x1/y1','x2/y2'].toSet()
+            assert resultMapForDmi1.dmiCmSubscriptionPredicates[0].xpaths
+                && resultMapForDmi2.dmiCmSubscriptionPredicates[0].xpaths == ['/x1/y1', 'x2/y2'].toSet()
 
-            assert resultMapForDmi1.dmiCmNotificationSubscriptionPredicates[1].xpaths
-                    && resultMapForDmi2.dmiCmNotificationSubscriptionPredicates[1].xpaths == ['/x3/y3','x4/y4'].toSet()
+            assert resultMapForDmi1.dmiCmSubscriptionPredicates[1].xpaths
+                && resultMapForDmi2.dmiCmSubscriptionPredicates[1].xpaths == ['/x3/y3', 'x4/y4'].toSet()
     }
 
     def 'Get map for cm handle IDs by DMI service name'() {
         given: 'the predicate from the test request CM subscription event'
-            def targetFilter = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates().get(0).getTargetFilter()
+            def targetFilter = ncmpInEvent.getData().getPredicates().get(0).getTargetFilter()
         when: 'the method to group all target CM handles by DMI service name is called'
             def mapOfCMHandleIDsByDmi = objectUnderTest.groupTargetCmHandleIdsByDmi(targetFilter)
         then: 'the size of the resulting map is correct'
@@ -153,48 +170,59 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec {
 
     def 'Update subscription status in cache per DMI service name'() {
         given: 'populated cache'
-            def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates()
-            def subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId()
+            def predicates = ncmpInEvent.getData().getPredicates()
+            def subscriptionId = ncmpInEvent.getData().getSubscriptionId()
             objectUnderTest.add(subscriptionId, predicates)
         when: 'subscription status per dmi is updated in cache'
-            objectUnderTest.updateDmiCmNotificationSubscriptionStatusPerDmi(subscriptionId,'dmi-1', CmNotificationSubscriptionStatus.ACCEPTED)
+            objectUnderTest.updateDmiSubscriptionStatus(subscriptionId,'dmi-1', CmSubscriptionStatus.ACCEPTED)
         then: 'verify status has been updated in cache'
             def predicate = testCache.get(subscriptionId)
-            assert predicate.get('dmi-1').cmNotificationSubscriptionStatus == CmNotificationSubscriptionStatus.ACCEPTED
+            assert predicate.get('dmi-1').cmSubscriptionStatus == CmSubscriptionStatus.ACCEPTED
     }
 
     def 'Persist Cache into database per dmi'() {
-        given: 'populate cache'
-            def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates()
-            def subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId()
+        given: 'populated cache'
+            def predicates = ncmpInEvent.getData().getPredicates()
+            def subscriptionId = ncmpInEvent.getData().getSubscriptionId()
             objectUnderTest.add(subscriptionId, predicates)
         when: 'subscription is persisted in database'
             objectUnderTest.persistIntoDatabasePerDmi(subscriptionId,'dmi-1')
         then: 'persistence service is called the correct number of times per dmi'
-            4 * mockCmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(_,_,_,subscriptionId)
+            4 * mockCmSubscriptionPersistenceService.addCmSubscription(_,_,_,subscriptionId)
+    }
+
+    def 'Remove subscription from database per dmi'() {
+        given: 'populated cache'
+            def predicates = ncmpInEvent.getData().getPredicates()
+            def subscriptionId = ncmpInEvent.getData().getSubscriptionId()
+            objectUnderTest.add(subscriptionId, predicates)
+        when: 'subscription is persisted in database'
+            objectUnderTest.removeFromDatabase(subscriptionId,'dmi-1')
+        then: 'persistence service is called the correct number of times per dmi'
+            4 * mockCmSubscriptionPersistenceService.removeCmSubscription(_,_,_,subscriptionId)
     }
 
     def setUpTestEvent(){
         def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
-        def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class)
+        def testEventSent = jsonObjectMapper.convertJsonString(jsonData, NcmpInEvent.class)
         def testCloudEventSent = CloudEventBuilder.v1()
-                .withData(objectMapper.writeValueAsBytes(testEventSent))
-                .withId('subscriptionCreated')
-                .withType('subscriptionCreated')
-                .withSource(URI.create('some-resource'))
-                .withExtension('correlationid', 'test-cmhandle1').build()
+            .withData(objectMapper.writeValueAsBytes(testEventSent))
+            .withId('subscriptionCreated')
+            .withType('subscriptionCreated')
+            .withSource(URI.create('some-resource'))
+            .withExtension('correlationid', 'test-cmhandle1').build()
         def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent)
         def cloudEvent = consumerRecord.value()
 
-        cmNotificationSubscriptionNcmpInEvent = toTargetEvent(cloudEvent, CmNotificationSubscriptionNcmpInEvent.class);
+        ncmpInEvent = toTargetEvent(cloudEvent, NcmpInEvent.class);
     }
 
     def initialiseMockInventoryPersistenceResponses(){
         mockInventoryPersistence.getYangModelCmHandles(['ch1','ch2'])
-                >> [yangModelCmHandle1, yangModelCmHandle2]
+            >> [yangModelCmHandle1, yangModelCmHandle2]
 
         mockInventoryPersistence.getYangModelCmHandles(['ch3','ch4'])
-                >> [yangModelCmHandle3, yangModelCmHandle4]
+            >> [yangModelCmHandle3, yangModelCmHandle4]
     }
 
-}
\ No newline at end of file
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.avc
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.cmavc
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
@@ -28,9 +28,9 @@ import io.cloudevents.kafka.impl.KafkaHeaders
 import org.apache.kafka.clients.consumer.ConsumerRecord
 import org.apache.kafka.clients.consumer.KafkaConsumer
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
 import org.onap.cps.ncmp.events.avc1_0_0.AvcEvent
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
@@ -39,18 +39,18 @@ import org.springframework.test.annotation.DirtiesContext
 import org.testcontainers.spock.Testcontainers
 import java.time.Duration
 
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
+import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent
 
-@SpringBootTest(classes = [EventsPublisher, AvcEventConsumer, ObjectMapper, JsonObjectMapper])
+@SpringBootTest(classes = [EventsPublisher, CmAvcEventConsumer, ObjectMapper, JsonObjectMapper])
 @Testcontainers
 @DirtiesContext
-class AvcEventConsumerSpec extends MessagingBaseSpec {
+class CmAvcEventConsumerSpec extends MessagingBaseSpec {
 
     @SpringBean
     EventsPublisher eventsPublisher = new EventsPublisher<CloudEvent>(legacyEventKafkaTemplate, cloudEventKafkaTemplate)
 
     @SpringBean
-    AvcEventConsumer acvEventConsumer = new AvcEventConsumer(eventsPublisher)
+    CmAvcEventConsumer acvEventConsumer = new CmAvcEventConsumer(eventsPublisher)
 
     @Autowired
     JsonObjectMapper jsonObjectMapper
@@ -85,10 +85,10 @@ class AvcEventConsumerSpec extends MessagingBaseSpec {
             def convertedAvcEvent = toTargetEvent(cloudEvent, AvcEvent.class)
         and: 'we have correct headers forwarded where correlation id matches'
             assert KafkaHeaders.getParsedKafkaHeader(record.headers(), 'ce_correlationid') == 'test-cmhandle1'
-        and: 'event id differs(as per requirement) between consumed and forwarded'
-            assert KafkaHeaders.getParsedKafkaHeader(record.headers(), 'ce_id') != 'sample-eventid'
+        and: 'event id is same between consumed and forwarded'
+            assert KafkaHeaders.getParsedKafkaHeader(record.headers(), 'ce_id') == 'sample-eventid'
         and: 'the event payload still matches'
             assert testEventSent == convertedAvcEvent
     }
 
-}
\ No newline at end of file
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiCmSubscriptionDetailsPerDmiMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/dmi/DmiCmSubscriptionDetailsPerDmiMapperSpec.groovy
new file mode 100644 (file)
index 0000000..5d74f45
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.dmi
+
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionKey
+import spock.lang.Specification
+
+class DmiCmSubscriptionDetailsPerDmiMapperSpec extends Specification {
+
+    def objectUnderTest = new DmiCmSubscriptionDetailsPerDmiMapper()
+
+    def 'Check for grouping of Dmi Subscription Details'() {
+        given: 'details in the form of datastore , cmhandle and xpath'
+            def subscribersPerDmi = [
+                'dmi-1': [
+                    new DmiCmSubscriptionKey('ncmp-datastore:passthrough-operational', 'ch-1', '/a/b'),
+                    new DmiCmSubscriptionKey('ncmp-datastore:passthrough-operational', 'ch-2', '/a/b')
+                ],
+                'dmi-2': [
+                    new DmiCmSubscriptionKey('ncmp-datastore:passthrough-running', 'ch-3', '/c/d'),
+                    new DmiCmSubscriptionKey('ncmp-datastore:passthrough-running', 'ch-3', '/e/f')
+                ]
+            ]
+        when: 'we try to map the values based on datastore and xpath'
+            def result = objectUnderTest.toDmiCmSubscriptionsPerDmi(subscribersPerDmi)
+        then: 'the mapped values are grouped as expected for dmi-1'
+            assert result['dmi-1'].dmiCmSubscriptionPredicates.size() == 1
+            assert result['dmi-1'].dmiCmSubscriptionPredicates[0].targetCmHandleIds.containsAll(['ch-1', 'ch-2'])
+        and: 'similarly for dmi-2'
+            assert result['dmi-2'].dmiCmSubscriptionPredicates.size() == 2
+            assert result['dmi-2'].dmiCmSubscriptionPredicates[0].targetCmHandleIds.contains('ch-3')
+            assert result['dmi-2'].dmiCmSubscriptionPredicates[1].targetCmHandleIds.contains('ch-3')
+    }
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi
 
 
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import spock.lang.Specification
 
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
 
-class CmNotificationSubscriptionDmiInEventMapperSpec extends Specification {
+class DmiInEventMapperSpec extends Specification {
 
     def mockInventoryPersistence = Mock(InventoryPersistence)
 
-    def objectUnderTest = new CmNotificationSubscriptionDmiInEventMapper(mockInventoryPersistence)
+    def objectUnderTest = new DmiInEventMapper(mockInventoryPersistence)
 
     def setup() {
         def yangModelCmHandles = [new YangModelCmHandle(id: 'ch-1', dmiProperties: [new YangModelCmHandle.Property('k1', 'v1')], publicProperties: []),
@@ -43,15 +43,15 @@ class CmNotificationSubscriptionDmiInEventMapperSpec extends Specification {
 
     def 'Check for Cm Notification Subscription DMI In Event mapping'() {
         given: 'a collection of cm subscription predicates'
-            def dmiCmNotificationSubscriptionPredicates = [new DmiCmNotificationSubscriptionPredicate(['ch-1'].toSet(), PASSTHROUGH_RUNNING, ['/ch-1'].toSet()),
-                                                           new DmiCmNotificationSubscriptionPredicate(['ch-2'].toSet(), PASSTHROUGH_OPERATIONAL, ['/ch-2'].toSet())]
+            def dmiSubscriptionPredicates = [new DmiCmSubscriptionPredicate(['ch-1'].toSet(), PASSTHROUGH_RUNNING, ['/ch-1'].toSet()),
+                                                           new DmiCmSubscriptionPredicate(['ch-2'].toSet(), PASSTHROUGH_OPERATIONAL, ['/ch-2'].toSet())]
         when: 'we try to map the values'
-            def result = objectUnderTest.toCmNotificationSubscriptionDmiInEvent(dmiCmNotificationSubscriptionPredicates)
+            def result = objectUnderTest.toDmiInEvent(dmiSubscriptionPredicates)
         then: 'it contains correct cm notification subscription cmhandle object'
-            assert result.data.cmhandles.cmhandleId.containsAll(['ch-1', 'ch-2'])
-            assert result.data.cmhandles.privateProperties.containsAll([['k1': 'v1'], ['k2': 'v2']])
+            assert result.data.cmHandles.cmhandleId.containsAll(['ch-1', 'ch-2'])
+            assert result.data.cmHandles.privateProperties.containsAll([['k1': 'v1'], ['k2': 'v2']])
         and: 'also has the correct dmi cm notification subscription predicates'
             assert result.data.predicates.targetFilter.containsAll([['ch-1'], ['ch-2']])
 
     }
-}
\ No newline at end of file
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer
-import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Cmhandle
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Data
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.CmHandle
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.Data
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.DmiInEvent
+import org.onap.cps.ncmp.utils.events.CloudEventMapper
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Specification
 
-class CmNotificationSubscriptionDmiInEventProducerSpec extends Specification {
+class DmiInEventProducerSpec extends Specification {
 
     def mockEventsPublisher = Mock(EventsPublisher)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
 
-    def objectUnderTest = new CmNotificationSubscriptionDmiInEventProducer(mockEventsPublisher, jsonObjectMapper)
+    def objectUnderTest = new DmiInEventProducer(mockEventsPublisher, jsonObjectMapper)
 
     def 'Create and Publish Cm Notification Subscription DMI In Event'() {
         given: 'a cm subscription for a dmi plugin'
             def subscriptionId = 'test-subscription-id'
             def dmiPluginName = 'test-dmiplugin'
             def eventType = 'subscriptionCreateRequest'
-            def cmNotificationSubscriptionDmiInEvent = new CmNotificationSubscriptionDmiInEvent(data: new Data(cmhandles: [new Cmhandle(cmhandleId: 'test-1', privateProperties: [:])]))
+            def dmiInEvent = new DmiInEvent(data: new Data(cmHandles: [new CmHandle(cmhandleId: 'test-1', privateProperties: [:])]))
         and: 'also we have target topic for dmiPlugin'
-            objectUnderTest.cmNotificationSubscriptionDmiInEventTopic = 'dmiplugin-test-topic'
+            objectUnderTest.dmiInEventTopic = 'dmiplugin-test-topic'
         when: 'the event is published'
-            objectUnderTest.publishCmNotificationSubscriptionDmiInEvent(subscriptionId, dmiPluginName, eventType, cmNotificationSubscriptionDmiInEvent)
+            objectUnderTest.publishDmiInEvent(subscriptionId, dmiPluginName, eventType, dmiInEvent)
         then: 'the event contains the required attributes'
             1 * mockEventsPublisher.publishCloudEvent(_, _, _) >> {
                 args ->
                     {
                         assert args[0] == 'dmiplugin-test-topic'
                         assert args[1] == subscriptionId
-                        def cmNotificationSubscriptionDmiInEventAsCloudEvent = (args[2] as CloudEvent)
-                        assert cmNotificationSubscriptionDmiInEventAsCloudEvent.getExtension('correlationid') == subscriptionId + '#' + dmiPluginName
-                        assert cmNotificationSubscriptionDmiInEventAsCloudEvent.type == 'subscriptionCreateRequest'
-                        assert cmNotificationSubscriptionDmiInEventAsCloudEvent.source.toString() == 'NCMP'
-                        assert CloudEventMapper.toTargetEvent(cmNotificationSubscriptionDmiInEventAsCloudEvent, CmNotificationSubscriptionDmiInEvent) == cmNotificationSubscriptionDmiInEvent
+                        def dmiInEventAsCloudEvent = (args[2] as CloudEvent)
+                        assert dmiInEventAsCloudEvent.getExtension('correlationid') == subscriptionId + '#' + dmiPluginName
+                        assert dmiInEventAsCloudEvent.type == 'subscriptionCreateRequest'
+                        assert dmiInEventAsCloudEvent.source.toString() == 'NCMP'
+                        assert CloudEventMapper.toTargetEvent(dmiInEventAsCloudEvent, DmiInEvent) == dmiInEvent
                     }
             }
     }
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi
 
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
@@ -28,20 +28,23 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
 import io.cloudevents.core.builder.CloudEventBuilder
 import org.apache.kafka.clients.consumer.ConsumerRecord
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer.CmNotificationSubscriptionDmiOutEventConsumer
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.Data
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp.NcmpOutEventMapper
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp.NcmpOutEventProducer
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.dmi_to_ncmp.Data
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.dmi_to_ncmp.DmiOutEvent
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.slf4j.LoggerFactory
-import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.ACCEPTED
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.REJECTED
+
 @SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
-class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpec {
+class DmiOutEventConsumerSpec extends MessagingBaseSpec {
 
     @Autowired
     JsonObjectMapper jsonObjectMapper
@@ -49,27 +52,27 @@ class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpe
     @Autowired
     ObjectMapper objectMapper
 
-    def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler)
-    def mockCmNotificationSubscriptionEventsHandler = Mock(CmNotificationSubscriptionEventsHandler)
-    def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler)
+    def mockDmiCacheHandler = Mock(DmiCacheHandler)
+    def mockNcmpOutEventProducer = Mock(NcmpOutEventProducer)
+    def mockNcmpOutEventMapper = Mock(NcmpOutEventMapper)
 
-    def objectUnderTest = new CmNotificationSubscriptionDmiOutEventConsumer(mockDmiCmNotificationSubscriptionCacheHandler, mockCmNotificationSubscriptionEventsHandler, mockCmNotificationSubscriptionMappersHandler)
+    def objectUnderTest = new DmiOutEventConsumer(mockDmiCacheHandler, mockNcmpOutEventProducer, mockNcmpOutEventMapper)
     def logger = Spy(ListAppender<ILoggingEvent>)
 
     void setup() {
-        ((Logger) LoggerFactory.getLogger(CmNotificationSubscriptionDmiOutEventConsumer.class)).addAppender(logger)
+        ((Logger) LoggerFactory.getLogger(DmiOutEventConsumer.class)).addAppender(logger)
         logger.start()
     }
 
     void cleanup() {
-        ((Logger) LoggerFactory.getLogger(CmNotificationSubscriptionDmiOutEventConsumer.class)).detachAndStopAllAppenders()
+        ((Logger) LoggerFactory.getLogger(DmiOutEventConsumer.class)).detachAndStopAllAppenders()
     }
 
 
     def 'Consume valid CM Subscription response from DMI Plugin'() {
         given: 'a cmsubscription event'
             def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionDmiOutEvent.json')
-            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionDmiOutEvent.class)
+            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, DmiOutEvent.class)
             def testCloudEventSent = CloudEventBuilder.v1()
                 .withData(objectMapper.writeValueAsBytes(testEventSent))
                 .withId('random-uuid')
@@ -78,7 +81,7 @@ class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpe
                 .withExtension('correlationid', 'sub-1#test-dmi-plugin-name').build()
             def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent)
         when: 'the valid event is consumed'
-            objectUnderTest.consumeCmNotificationSubscriptionDmiOutEvent(consumerRecord)
+            objectUnderTest.consumeDmiOutEvent(consumerRecord)
         then: 'an event is logged with level INFO'
             def loggingEvent = getLoggingEvent()
             assert loggingEvent.level == Level.INFO
@@ -89,28 +92,28 @@ class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpe
     def 'Consume a valid CM Notification Subscription Event and perform correct actions base on status'() {
         given: 'a cmNotificationSubscription event'
             def dmiOutEventData = new Data(statusCode: statusCode, statusMessage: subscriptionStatus.toString())
-            def cmNotificationSubscriptionDmiOutEvent = new CmNotificationSubscriptionDmiOutEvent().withData(dmiOutEventData)
+            def dmiOutEvent = new DmiOutEvent().withData(dmiOutEventData)
             def testCloudEventSent = CloudEventBuilder.v1()
-                .withData(objectMapper.writeValueAsBytes(cmNotificationSubscriptionDmiOutEvent))
+                .withData(objectMapper.writeValueAsBytes(dmiOutEvent))
                 .withId('random-uuid')
                 .withType('subscriptionCreateResponse')
                 .withSource(URI.create('test-dmi-plugin-name'))
                 .withExtension('correlationid', 'sub-1#test-dmi-plugin-name').build()
             def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent)
         when: 'the event is consumed'
-            objectUnderTest.consumeCmNotificationSubscriptionDmiOutEvent(consumerRecord)
+            objectUnderTest.consumeDmiOutEvent(consumerRecord)
         then: 'correct number of calls to cache'
-            expectedCacheCalls * mockDmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi('sub-1','test-dmi-plugin-name', subscriptionStatus)
+            expectedCacheCalls * mockDmiCacheHandler.updateDmiSubscriptionStatus('sub-1','test-dmi-plugin-name', subscriptionStatus)
         and: 'correct number of calls to persist cache'
-            expectedPersistenceCalls * mockDmiCmNotificationSubscriptionCacheHandler.persistIntoDatabasePerDmi('sub-1','test-dmi-plugin-name')
+            expectedPersistenceCalls * mockDmiCacheHandler.persistIntoDatabasePerDmi('sub-1','test-dmi-plugin-name')
         and: 'correct number of calls to map the ncmp out event'
-            1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent('sub-1', _)
+            1 * mockNcmpOutEventMapper.toNcmpOutEvent('sub-1', _)
         and: 'correct number of calls to publish the ncmp out event to client'
-            1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent('sub-1', 'subscriptionCreateResponse', _, false)
+            1 * mockNcmpOutEventProducer.publishNcmpOutEvent('sub-1', 'subscriptionCreateResponse', _, false)
         where: 'the following parameters are used'
-            scenario            | subscriptionStatus                            | statusCode || expectedCacheCalls | expectedPersistenceCalls
-            'Accepted Status'   | CmNotificationSubscriptionStatus.ACCEPTED     | '1'        || 1                  | 1
-            'Rejected Status'   | CmNotificationSubscriptionStatus.REJECTED     | '2'        || 1                  | 0
+            scenario          | subscriptionStatus | statusCode || expectedCacheCalls | expectedPersistenceCalls
+            'Accepted Status' | ACCEPTED           | '1'        || 1                  | 1
+            'Rejected Status' | REJECTED           | '104'      || 1                  | 0
     }
 
     def getLoggingEvent() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparatorSpec.groovy
new file mode 100644 (file)
index 0000000..0ebf9a6
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (c) 2024 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.impl.cmnotificationsubscription.ncmp
+
+
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+
+class CmSubscriptionComparatorSpec extends Specification {
+
+    def mockCmSubscriptionPersistenceService = Mock(CmSubscriptionPersistenceService)
+    def objectUnderTest = new CmSubscriptionComparator(mockCmSubscriptionPersistenceService)
+
+    def 'Find Delta of given list of predicates'() {
+        given: 'A list of predicates'
+            def predicates = [new DmiCmSubscriptionPredicate(['ch-1', 'ch-2'].toSet(), PASSTHROUGH_OPERATIONAL, ['a/1/', 'b/2'].toSet())]
+        and: '3 positive responses and 1 negative.'
+            mockCmSubscriptionPersistenceService.isOngoingCmSubscription(PASSTHROUGH_OPERATIONAL, 'ch-1', 'a/1/') >>> true
+            mockCmSubscriptionPersistenceService.isOngoingCmSubscription(PASSTHROUGH_OPERATIONAL, 'ch-1', 'b/2') >>> true
+            mockCmSubscriptionPersistenceService.isOngoingCmSubscription(PASSTHROUGH_OPERATIONAL, 'ch-2', 'a/1/') >>> true
+            mockCmSubscriptionPersistenceService.isOngoingCmSubscription(PASSTHROUGH_OPERATIONAL, 'ch-2', 'b/2') >>> false
+        when: 'getDelta is called'
+            def result = objectUnderTest.getNewDmiSubscriptionPredicates(predicates)
+        then: 'verify correct delta is returned'
+            assert result.size() == 1
+            assert result[0].targetCmHandleIds[0] == 'ch-2'
+            assert result[0].xpaths[0] == 'b/2'
+
+    }
+
+    def 'Find Delta of given list of predicates when it is an ongoing Cm Subscription'() {
+        given: 'A list of predicates'
+            def predicates = [new DmiCmSubscriptionPredicate(['ch-1'].toSet(), PASSTHROUGH_OPERATIONAL, ['a/1/'].toSet())]
+        and: 'its already present'
+            mockCmSubscriptionPersistenceService.isOngoingCmSubscription(PASSTHROUGH_OPERATIONAL, 'ch-1', 'a/1/') >>> true
+        when: 'getDelta is called'
+            def result = objectUnderTest.getNewDmiSubscriptionPredicates(predicates)
+        then: 'verify correct delta is returned'
+            assert result.size() == 0
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionHandlerImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionHandlerImplSpec.groovy
new file mode 100644 (file)
index 0000000..f902c60
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (c) 2024 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.impl.cmnotificationsubscription.ncmp
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiCmSubscriptionDetailsPerDmiMapper
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiInEventMapper
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.dmi.DmiInEventProducer
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.NcmpInEvent
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_dmi.DmiInEvent
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.spi.model.DataNode
+import org.onap.cps.utils.JsonObjectMapper
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.ACCEPTED
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.PENDING
+
+class CmSubscriptionHandlerImplSpec extends Specification {
+
+    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    def mockCmSubscriptionPersistenceService = Mock(CmSubscriptionPersistenceService)
+    def mockCmSubscriptionComparator = Mock(CmSubscriptionComparator)
+    def mockNcmpOutEventMapper = Mock(NcmpOutEventMapper)
+    def mockDmiInEventMapper = Mock(DmiInEventMapper)
+    def dmiCmSubscriptionDetailsPerDmiMapper = new DmiCmSubscriptionDetailsPerDmiMapper()
+    def mockNcmpOutEventProducer = Mock(NcmpOutEventProducer)
+    def mockDmiInEventProducer = Mock(DmiInEventProducer)
+    def mockDmiCacheHandler = Mock(DmiCacheHandler)
+    def mockInventoryPersistence = Mock(InventoryPersistence)
+
+    def objectUnderTest = new CmSubscriptionHandlerImpl(mockCmSubscriptionPersistenceService,
+        mockCmSubscriptionComparator, mockNcmpOutEventMapper, mockDmiInEventMapper, dmiCmSubscriptionDetailsPerDmiMapper,
+        mockNcmpOutEventProducer, mockDmiInEventProducer, mockDmiCacheHandler, mockInventoryPersistence)
+
+    def testDmiSubscriptionsPerDmi = ["dmi-1": new DmiCmSubscriptionDetails([], PENDING)]
+
+    def 'Consume valid and unique CmNotificationSubscriptionNcmpInEvent create message'() {
+        given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id'
+            def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
+            def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, NcmpInEvent.class)
+            def testListOfDeltaPredicates = [new DmiCmSubscriptionPredicate(['ch1'].toSet(), PASSTHROUGH_OPERATIONAL, ['/a/b'].toSet())]
+            mockCmSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true
+        and: 'relevant details is extracted from the event'
+            def subscriptionId = testEventConsumed.getData().getSubscriptionId()
+            def predicates = testEventConsumed.getData().getPredicates()
+        and: 'the cache handler returns for relevant subscription id'
+            1 * mockDmiCacheHandler.get("test-id") >> testDmiSubscriptionsPerDmi
+        and: 'the delta predicates is returned'
+            1 * mockCmSubscriptionComparator.getNewDmiSubscriptionPredicates(_) >> testListOfDeltaPredicates
+        and: 'the DMI in event mapper returns cm notification subscription event'
+            def testDmiInEvent = new DmiInEvent()
+            1 * mockDmiInEventMapper.toDmiInEvent(testListOfDeltaPredicates) >> testDmiInEvent
+        when: 'the valid and unique event is consumed'
+            objectUnderTest.processSubscriptionCreateRequest(subscriptionId, predicates)
+        then: 'the subscription cache handler is called once'
+            1 * mockDmiCacheHandler.add('test-id', _)
+        and: 'the events handler method to publish DMI event is called correct number of times with the correct parameters'
+            testDmiSubscriptionsPerDmi.size() * mockDmiInEventProducer.publishDmiInEvent(
+                "test-id", "dmi-1", "subscriptionCreateRequest", testDmiInEvent)
+        and: 'we schedule to send the response after configured time from the cache'
+            1 * mockNcmpOutEventProducer.publishNcmpOutEvent('test-id', 'subscriptionCreateResponse', null, true)
+    }
+
+    def 'Consume valid and Overlapping Cm Notification Subscription NcmpIn Event'() {
+        given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id'
+            def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
+            def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, NcmpInEvent.class)
+            def noDeltaPredicates = []
+            mockCmSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true
+        and: 'the cache handler returns for relevant subscription id'
+            1 * mockDmiCacheHandler.get('test-id') >> testDmiSubscriptionsPerDmi
+        and: 'the delta predicates is returned'
+            1 * mockCmSubscriptionComparator.getNewDmiSubscriptionPredicates(_) >> noDeltaPredicates
+        when: 'the valid and unique event is consumed'
+            objectUnderTest.processSubscriptionCreateRequest('test-id', noDeltaPredicates)
+        then: 'the subscription cache handler is called once'
+            1 * mockDmiCacheHandler.add('test-id', _)
+        and: 'the subscription details are updated in the cache'
+            1 * mockDmiCacheHandler.updateDmiSubscriptionStatus('test-id', _, ACCEPTED)
+        and: 'we schedule to send the response after configured time from the cache'
+            1 * mockNcmpOutEventProducer.publishNcmpOutEvent('test-id', 'subscriptionCreateResponse', null, true)
+    }
+
+    def 'Consume valid and but non-unique CmNotificationSubscription create message'() {
+        given: 'a cmNotificationSubscriptionNcmp in event'
+            def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
+            def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, NcmpInEvent.class)
+            mockCmSubscriptionPersistenceService.isUniqueSubscriptionId('test-id') >> false
+        and: 'relevant details is extracted from the event'
+            def subscriptionId = testEventConsumed.getData().getSubscriptionId()
+            def predicates = testEventConsumed.getData().getPredicates()
+        and: 'the NCMP out in event mapper returns an event for rejected request'
+            def testNcmpOutEvent = new NcmpOutEvent()
+            1 * mockNcmpOutEventMapper.toNcmpOutEventForRejectedRequest(
+                "test-id", _) >> testNcmpOutEvent
+        when: 'the valid but non-unique event is consumed'
+            objectUnderTest.processSubscriptionCreateRequest(subscriptionId, predicates)
+        then: 'the events handler method to publish DMI event is never called'
+            0 * mockDmiInEventProducer.publishDmiInEvent(_, _, _, _)
+        and: 'the events handler method to publish NCMP out event is called once'
+            1 * mockNcmpOutEventProducer.publishNcmpOutEvent('test-id', 'subscriptionCreateResponse', testNcmpOutEvent, false)
+    }
+
+    def 'Consume valid CmNotificationSubscriptionNcmpInEvent delete message'() {
+        given: 'a test subscription id'
+            def subscriptionId = 'test-id'
+        and: 'the persistence service returns datanodes'
+            1 * mockCmSubscriptionPersistenceService.getAllNodesForSubscriptionId(subscriptionId) >>
+                [new DataNode(xpath: "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='x/y']", leaves: ['xpath': 'x/y', 'subscriptionIds': ['test-id']]),
+                new DataNode(xpath: "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-2']/filters/filter[@xpath='y/z']", leaves: ['xpath': 'y/z', 'subscriptionIds': ['test-id']])]
+        and: 'the inventory persistence returns yang model cm handles'
+            1 * mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(dmiServiceName: 'dmi-1')
+            1 * mockInventoryPersistence.getYangModelCmHandle('ch-2') >> new YangModelCmHandle(dmiServiceName: 'dmi-2')
+        when: 'the subscription delete request is processed'
+            objectUnderTest.processSubscriptionDeleteRequest(subscriptionId)
+        then: 'the method to publish a dmi event is called with correct parameters'
+            1 * mockDmiInEventProducer.publishDmiInEvent(subscriptionId,'dmi-1','subscriptionDeleteRequest',_)
+            1 * mockDmiInEventProducer.publishDmiInEvent(subscriptionId,'dmi-2','subscriptionDeleteRequest',_)
+        and: 'the method to publish nmcp out event is called with correct parameters'
+            1 * mockNcmpOutEventProducer.publishNcmpOutEvent(subscriptionId, 'subscriptionDeleteResponse', null, true)
+    }
+
+    def 'Delete a subscriber for fully overlapping subscriptions'() {
+        given: 'a test subscription id'
+            def subscriptionId = 'test-id'
+        and: 'the persistence service returns datanodes with multiple subscribers'
+            1 * mockCmSubscriptionPersistenceService.getAllNodesForSubscriptionId(subscriptionId) >>
+                [new DataNode(xpath: "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='x/y']", leaves: ['xpath': 'x/y', 'subscriptionIds': ['test-id','other-id']]),
+                 new DataNode(xpath: "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-2']/filters/filter[@xpath='y/z']", leaves: ['xpath': 'y/z', 'subscriptionIds': ['test-id','other-id']])]
+        and: 'the inventory persistence returns yang model cm handles'
+            1 * mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(dmiServiceName: 'dmi-1')
+            1 * mockInventoryPersistence.getYangModelCmHandle('ch-2') >> new YangModelCmHandle(dmiServiceName: 'dmi-2')
+        and: 'the cache handler returns the relevant maps whenever called'
+            2 * mockDmiCacheHandler.get(subscriptionId) >> ['dmi-1':[:],'dmi-2':[:]]
+        when: 'the subscription delete request is processed'
+            objectUnderTest.processSubscriptionDeleteRequest(subscriptionId)
+        then: 'the method to publish a dmi event is never called'
+            0 * mockDmiInEventProducer.publishDmiInEvent(_,_,_,_)
+        and: 'the cache handler is called to remove subscriber from database per dmi'
+            1 * mockDmiCacheHandler.removeFromDatabase('test-id', 'dmi-1')
+            1 * mockDmiCacheHandler.removeFromDatabase('test-id', 'dmi-2')
+        and: 'the method to publish nmcp out event is called with correct parameters'
+            1 * mockNcmpOutEventProducer.publishNcmpOutEvent(subscriptionId, 'subscriptionDeleteResponse', null, false)
+    }
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp
 
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
@@ -28,21 +28,19 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
 import io.cloudevents.core.builder.CloudEventBuilder
 import org.apache.kafka.clients.consumer.ConsumerRecord
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer.CmNotificationSubscriptionNcmpInEventConsumer
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionHandlerService
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
-import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.NcmpInEvent
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.slf4j.LoggerFactory
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 
 @SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
-class CmNotificationSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpec {
+class NcmpInEventConsumerSpec extends MessagingBaseSpec {
 
-    def mockCmNotificationSubscriptionHandlerService = Mock(CmNotificationSubscriptionHandlerService)
-    def objectUnderTest = new CmNotificationSubscriptionNcmpInEventConsumer(mockCmNotificationSubscriptionHandlerService)
+    def mockCmSubscriptionHandler = Mock(CmSubscriptionHandler)
+    def objectUnderTest = new NcmpInEventConsumer(mockCmSubscriptionHandler)
     def logger = Spy(ListAppender<ILoggingEvent>)
 
     @Autowired
@@ -52,19 +50,19 @@ class CmNotificationSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpe
     ObjectMapper objectMapper
 
     void setup() {
-        ((Logger) LoggerFactory.getLogger(CmNotificationSubscriptionNcmpInEventConsumer.class)).addAppender(logger)
+        ((Logger) LoggerFactory.getLogger(NcmpInEventConsumer.class)).addAppender(logger)
         logger.start()
     }
 
     void cleanup() {
-        ((Logger) LoggerFactory.getLogger(CmNotificationSubscriptionNcmpInEventConsumer.class)).detachAndStopAllAppenders()
+        ((Logger) LoggerFactory.getLogger(NcmpInEventConsumer.class)).detachAndStopAllAppenders()
     }
 
 
     def 'Consume valid CmNotificationSubscriptionNcmpInEvent create message'() {
         given: 'a cmNotificationSubscription event'
             def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
-            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class)
+            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, NcmpInEvent.class)
             def testCloudEventSent = CloudEventBuilder.v1()
                 .withData(objectMapper.writeValueAsBytes(testEventSent))
                 .withId('subscriptionCreated')
@@ -72,19 +70,40 @@ class CmNotificationSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpe
                 .withSource(URI.create('some-resource'))
                 .withExtension('correlationid', 'test-cmhandle1').build()
             def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent)
-        and: 'notifications are enabled'
-            objectUnderTest.notificationFeatureEnabled = true
         when: 'the valid event is consumed'
             objectUnderTest.consumeSubscriptionEvent(consumerRecord)
         then: 'an event is logged with level INFO'
             def loggingEvent = getLoggingEvent()
             assert loggingEvent.level == Level.INFO
         and: 'the log indicates the task completed successfully'
-            assert loggingEvent.formattedMessage == 'Subscription for source some-resource with subscription id test-id ...'
+            assert loggingEvent.formattedMessage == 'Subscription create request for source some-resource with subscription id test-id ...'
         and: 'the subscription handler service is called once'
-            1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest(_)
+            1 * mockCmSubscriptionHandler.processSubscriptionCreateRequest('test-id',_)
     }
 
+    def 'Consume valid CmNotificationSubscriptionNcmpInEvent delete message'() {
+        given: 'a cmNotificationSubscription event'
+            def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json')
+            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, NcmpInEvent.class)
+            def testCloudEventSent = CloudEventBuilder.v1()
+                .withData(objectMapper.writeValueAsBytes(testEventSent))
+                .withId('sub-id')
+                .withType('subscriptionDeleteRequest')
+                .withSource(URI.create('some-resource'))
+                .withExtension('correlationid', 'test-cmhandle1').build()
+            def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent)
+        when: 'the valid event is consumed'
+            objectUnderTest.consumeSubscriptionEvent(consumerRecord)
+        then: 'an event is logged with level INFO'
+            def loggingEvent = getLoggingEvent()
+            assert loggingEvent.level == Level.INFO
+        and: 'the log indicates the task completed successfully'
+            assert loggingEvent.formattedMessage == 'Subscription delete request for source some-resource with subscription id test-id ...'
+        and: 'the subscription handler service is called once'
+            1 * mockCmSubscriptionHandler.processSubscriptionDeleteRequest('test-id')
+    }
+
+
     def getLoggingEvent() {
         return logger.list[1]
     }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventMapperSpec.groovy
new file mode 100644 (file)
index 0000000..d3c4026
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.cmnotificationsubscription.ncmp
+
+
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionDetails
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.models.DmiCmSubscriptionPredicate
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.ACCEPTED
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.PENDING
+import static org.onap.cps.ncmp.impl.cmnotificationsubscription.models.CmSubscriptionStatus.REJECTED
+
+class NcmpOutEventMapperSpec extends Specification {
+
+    static Map<String, DmiCmSubscriptionDetails> dmiSubscriptionsPerDmi
+
+    def objectUnderTest = new NcmpOutEventMapper()
+
+    def setup() {
+        def dmiSubscriptionPredicateA = new DmiCmSubscriptionPredicate(['ch-A'] as Set, PASSTHROUGH_RUNNING, ['/a'] as Set)
+        def dmiSubscriptionPredicateB = new DmiCmSubscriptionPredicate(['ch-B'] as Set, PASSTHROUGH_OPERATIONAL, ['/b'] as Set)
+        def dmiSubscriptionPredicateC = new DmiCmSubscriptionPredicate(['ch-C'] as Set, PASSTHROUGH_OPERATIONAL, ['/c'] as Set)
+        dmiSubscriptionsPerDmi = ['dmi-1': new DmiCmSubscriptionDetails([dmiSubscriptionPredicateA], PENDING),
+                                  'dmi-2': new DmiCmSubscriptionDetails([dmiSubscriptionPredicateB], ACCEPTED),
+                                  'dmi-3': new DmiCmSubscriptionDetails([dmiSubscriptionPredicateC], REJECTED)
+        ]
+    }
+
+    def 'Check for Cm Notification Subscription Outgoing event mapping'() {
+        when: 'we try to map the event to send it to client'
+            def result = objectUnderTest.toNcmpOutEvent('test-subscription', dmiSubscriptionsPerDmi)
+        then: 'event is mapped correctly for the subscription'
+            result.data.subscriptionId == 'test-subscription'
+        and: 'the cm handle ids are part of correct list'
+            result.data.pendingTargets == ['ch-A'] as Set
+            result.data.acceptedTargets == ['ch-B'] as Set
+            result.data.rejectedTargets == ['ch-C'] as Set
+    }
+
+    def 'Check for Cm Notification Rejected Subscription Outgoing event mapping'() {
+        when: 'we try to map the event to send it to client'
+            def result = objectUnderTest.toNcmpOutEventForRejectedRequest('test-subscription', ['ch-1', 'ch-2'])
+        then: 'event is mapped correctly for the subscription id'
+            result.data.subscriptionId == 'test-subscription'
+        and: 'the cm handle ids are part of correct list'
+            result.data.withRejectedTargets(['ch-1', 'ch-2'])
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/NcmpOutEventProducerSpec.groovy
new file mode 100644 (file)
index 0000000..afa2e98
--- /dev/null
@@ -0,0 +1,106 @@
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.ncmp
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.cloudevents.CloudEvent
+import org.onap.cps.events.EventsPublisher
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.cache.DmiCacheHandler
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.Data
+import org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent
+import org.onap.cps.ncmp.utils.events.CloudEventMapper
+import org.onap.cps.utils.JsonObjectMapper
+import spock.lang.Specification
+
+class NcmpOutEventProducerSpec extends Specification {
+
+    def mockEventsPublisher = Mock(EventsPublisher)
+    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    def mockNcmpOutEventMapper = Mock(NcmpOutEventMapper)
+    def mockDmiCacheHandler = Mock(DmiCacheHandler)
+
+    def objectUnderTest = new NcmpOutEventProducer(mockEventsPublisher, jsonObjectMapper, mockNcmpOutEventMapper, mockDmiCacheHandler)
+
+    def 'Create and #scenario Cm Notification Subscription NCMP out event'() {
+        given: 'a cm subscription response for the client'
+            def subscriptionId = 'test-subscription-id-2'
+            def eventType = 'subscriptionCreateResponse'
+            def ncmpOutEvent = new NcmpOutEvent(data: new Data(subscriptionId: 'test-subscription-id-2', acceptedTargets: ['ch-1', 'ch-2']))
+        and: 'also we have target topic for publishing to client'
+            objectUnderTest.ncmpOutEventTopic = 'client-test-topic'
+        and: 'a deadline to an event'
+            objectUnderTest.dmiOutEventTimeoutInMs = 1000
+        when: 'the event is published'
+            objectUnderTest.publishNcmpOutEvent(subscriptionId, eventType, ncmpOutEvent, eventPublishingTaskToBeScheduled)
+        then: 'we conditionally wait for a while'
+            Thread.sleep(delayInMs)
+        then: 'the event contains the required attributes'
+            1 * mockEventsPublisher.publishCloudEvent(_, _, _) >> {
+                args ->
+                    {
+                        assert args[0] == 'client-test-topic'
+                        assert args[1] == subscriptionId
+                        def ncmpOutEventAsCloudEvent = (args[2] as CloudEvent)
+                        assert ncmpOutEventAsCloudEvent.getExtension('correlationid') == subscriptionId
+                        assert ncmpOutEventAsCloudEvent.type == 'subscriptionCreateResponse'
+                        assert ncmpOutEventAsCloudEvent.source.toString() == 'NCMP'
+                        assert CloudEventMapper.toTargetEvent(ncmpOutEventAsCloudEvent, NcmpOutEvent) == ncmpOutEvent
+                    }
+            }
+        where: 'following scenarios are considered'
+            scenario                                          | delayInMs | eventPublishingTaskToBeScheduled
+            'publish event now'                               | 0         | false
+            'schedule and publish after the configured time ' | 1500      | true
+    }
+
+    def 'Schedule Cm Notification Subscription NCMP out event but later publish it on demand'() {
+        given: 'a cm subscription response for the client'
+            def subscriptionId = 'test-subscription-id-3'
+            def eventType = 'subscriptionCreateResponse'
+            def ncmpOutEvent = new NcmpOutEvent(data: new Data(subscriptionId: 'test-subscription-id-3', acceptedTargets: ['ch-2', 'ch-3']))
+        and: 'also we have target topic for publishing to client'
+            objectUnderTest.ncmpOutEventTopic = 'client-test-topic'
+        and: 'a deadline to an event'
+            objectUnderTest.dmiOutEventTimeoutInMs = 1000
+        when: 'the event is scheduled to be published'
+            objectUnderTest.publishNcmpOutEvent(subscriptionId, eventType, ncmpOutEvent, true)
+        then: 'we wait for 10ms and then we receive response from DMI'
+            Thread.sleep(10)
+        and: 'we receive response from DMI so we publish the message on demand'
+            objectUnderTest.publishNcmpOutEvent(subscriptionId, eventType, ncmpOutEvent, false)
+        then: 'the event contains the required attributes'
+            1 * mockEventsPublisher.publishCloudEvent(_, _, _) >> {
+                args ->
+                    {
+                        assert args[0] == 'client-test-topic'
+                        assert args[1] == subscriptionId
+                        def ncmpOutEventAsCloudEvent = (args[2] as CloudEvent)
+                        assert ncmpOutEventAsCloudEvent.getExtension('correlationid') == subscriptionId
+                        assert ncmpOutEventAsCloudEvent.type == 'subscriptionCreateResponse'
+                        assert ncmpOutEventAsCloudEvent.source.toString() == 'NCMP'
+                        assert CloudEventMapper.toTargetEvent(ncmpOutEventAsCloudEvent, NcmpOutEvent) == ncmpOutEvent
+                    }
+            }
+        then: 'the cache handler is called once to remove accepted and rejected entries in cache'
+            1 * mockDmiCacheHandler.removeAcceptedAndRejectedDmiSubscriptionEntries(subscriptionId)
+    }
+
+    def 'No event published when NCMP out event is null'() {
+        given: 'a cm subscription response for the client'
+            def subscriptionId = 'test-subscription-id-3'
+            def eventType = 'subscriptionCreateResponse'
+            def ncmpOutEvent = null
+        and: 'also we have target topic for publishing to client'
+            objectUnderTest.ncmpOutEventTopic = 'client-test-topic'
+        and: 'a deadline to an event'
+            objectUnderTest.dmiOutEventTimeoutInMs = 1000
+        when: 'the event is scheduled to be published'
+            objectUnderTest.publishNcmpOutEvent(subscriptionId, eventType, ncmpOutEvent, true)
+        then: 'we wait for 10ms and then we receive response from DMI'
+            Thread.sleep(10)
+        and: 'we receive NO response from DMI so we publish the message on demand'
+            objectUnderTest.publishNcmpOutEvent(subscriptionId, eventType, ncmpOutEvent, false)
+        and: 'no event published'
+            0 * mockEventsPublisher.publishCloudEvent(*_)
+    }
+
+
+}
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  * Copyright (c) 2024 Nordix Foundation.
+ * Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.cmsubscription.service
-
-import org.onap.cps.utils.ContentType
-
-import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID;
-import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE;
-import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH;
+package org.onap.cps.ncmp.impl.cmnotificationsubscription.utils
 
+import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsQueryService
-import org.onap.cps.ncmp.api.impl.operations.DatastoreType
-import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
+import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
-import com.fasterxml.jackson.databind.ObjectMapper
 import spock.lang.Specification
 
-class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification {
+import static CmSubscriptionPersistenceService.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE
+import static CmSubscriptionPersistenceService.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH
+import static CmSubscriptionPersistenceService.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+
+class CmSubscriptionPersistenceServiceSpec extends Specification {
 
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
     def mockCpsQueryService = Mock(CpsQueryService)
     def mockCpsDataService = Mock(CpsDataService)
 
-    def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
+    def objectUnderTest = new CmSubscriptionPersistenceService(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
 
     def 'Check ongoing cm subscription #scenario'() {
         given: 'a valid cm subscription query'
-            def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']";
+            def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']"
         and: 'datanodes optionally returned'
             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                    cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode
+                cpsPathQuery, OMIT_DESCENDANTS) >> dataNode
         when: 'we check for an ongoing cm subscription'
-            def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path')
+            def response = objectUnderTest.isOngoingCmSubscription(PASSTHROUGH_RUNNING, 'ch-1', '/cps/path')
         then: 'we get expected response'
             assert response == isOngoingCmSubscription
         where: 'following scenarios are used'
@@ -63,8 +65,8 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification
         given: 'a cps path with a subscription ID for querying'
             def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted('some-sub')
         and: 'relevant datanodes are returned'
-            1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >>
-                    dataNodes
+            1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, OMIT_DESCENDANTS) >>
+                dataNodes
         when: 'a subscription ID is tested for uniqueness'
             def result = objectUnderTest.isUniqueSubscriptionId('some-sub')
         then: 'result is as expected'
@@ -77,99 +79,126 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification
 
     def 'Add new subscriber to an ongoing cm notification subscription'() {
         given: 'a valid cm subscription path query'
-            def cpsPathQuery =CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
+            def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
         and: 'a dataNode exists for the given cps path query'
-             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
+            mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
+                cpsPathQuery, OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y', 'subscriptionIds': ['sub-1']])]
         when: 'the method to add/update cm notification subscription is called'
-            objectUnderTest.addCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1','/x/y', 'newSubId')
+            objectUnderTest.addCmSubscription(PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'newSubId')
         then: 'data service method to update list of subscribers is called once'
             1 * mockCpsDataService.updateNodeLeaves(
                 'NCMP-Admin',
                 'cm-data-subscriptions',
                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
-                objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1','newSubId']), _)
+                objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1', 'newSubId']), _, ContentType.JSON)
     }
 
     def 'Add new cm notification subscription for #datastoreType'() {
         given: 'a valid cm subscription path query'
             def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y')
-            def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
+            def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
         and: 'a parent node xpath for the cm subscription path above'
             def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles'
         and: 'a datanode does not exist for cm subscription path query'
             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-               cmSubscriptionCpsPathQuery,
-                FetchDescendantsOption.OMIT_DESCENDANTS) >> []
+                cmSubscriptionCpsPathQuery,
+                OMIT_DESCENDANTS) >> []
         and: 'a datanode does not exist for the given cm handle subscription path query'
             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                cmHandleForSubscriptionPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> []
+                cmHandleForSubscriptionPathQuery, OMIT_DESCENDANTS) >> []
         and: 'subscription is mapped as JSON'
             def subscriptionAsJson = '{"cm-handle":[{"id":"ch-1","filters":' +
                 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']) + '}]}'
         when: 'the method to add/update cm notification subscription is called'
-            objectUnderTest.addCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
+            objectUnderTest.addCmSubscription(datastoreType, 'ch-1', '/x/y', 'newSubId')
         then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
             1 * mockCpsDataService.saveData(
                 'NCMP-Admin',
                 'cm-data-subscriptions',
                 parentNodeXpath.formatted(datastoreName),
-                subscriptionAsJson,_, ContentType.JSON)
+                subscriptionAsJson, _, ContentType.JSON)
         where:
-            scenario                  | datastoreType                          || datastoreName
-            'passthrough_running'     | DatastoreType.PASSTHROUGH_RUNNING      || "ncmp-datastore:passthrough-running"
-            'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL  || "ncmp-datastore:passthrough-operational"
+            scenario                  | datastoreType           || datastoreName
+            'passthrough_running'     | PASSTHROUGH_RUNNING     || 'ncmp-datastore:passthrough-running'
+            'passthrough_operational' | PASSTHROUGH_OPERATIONAL || 'ncmp-datastore:passthrough-operational'
     }
 
     def 'Add new cm notification subscription when xpath does not exist for existing subscription cm handle'() {
         given: 'a valid cm subscription path query'
             def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y')
-            def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
+            def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
         and: 'a parent node xpath for given cm handle for subscription path above'
             def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles/cm-handle[@id=\'%s\']/filters'
         and: 'a datanode does not exist for cm subscription path query'
             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                cmSubscriptionCpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> []
+                cmSubscriptionCpsPathQuery, OMIT_DESCENDANTS) >> []
         and: 'a datanode exists for the given cm handle subscription path query'
             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                cmHandleForSubscriptionPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode()]
+                cmHandleForSubscriptionPathQuery, OMIT_DESCENDANTS) >> [new DataNode()]
         when: 'the method to add/update cm notification subscription is called'
-            objectUnderTest.addCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
+            objectUnderTest.addCmSubscription(datastoreType, 'ch-1', '/x/y', 'newSubId')
         then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
             1 * mockCpsDataService.saveListElements(
                 'NCMP-Admin',
                 'cm-data-subscriptions',
                 parentNodeXpath.formatted(datastoreName, 'ch-1'),
-                objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']),_)
+                objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']), _, ContentType.JSON)
         where:
-            scenario                  | datastoreType                          || datastoreName
-            'passthrough_running'     | DatastoreType.PASSTHROUGH_RUNNING      || "ncmp-datastore:passthrough-running"
-            'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL  || "ncmp-datastore:passthrough-operational"
+            scenario                  | datastoreType           || datastoreName
+            'passthrough_running'     | PASSTHROUGH_RUNNING     || 'ncmp-datastore:passthrough-running'
+            'passthrough_operational' | PASSTHROUGH_OPERATIONAL || 'ncmp-datastore:passthrough-operational'
     }
 
     def 'Remove subscriber from a list of an ongoing cm notification subscription'() {
         given: 'a subscription exists when queried'
             def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1', 'sub-2']])]
+                cpsPathQuery, OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y', 'subscriptionIds': ['sub-1', 'sub-2']])]
         when: 'the subscriber is removed'
-            objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
+            objectUnderTest.removeCmSubscription(PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
         then: 'the list of subscribers is updated'
             1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-subscriptions',
                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
-                objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _)
+                objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _, ContentType.JSON)
     }
 
-    def 'Removing last ongoing subscription for datastore, cmhandle and xpath'(){
+    def 'Removing last ongoing subscription for datastore and cmhandle and xpath'() {
         given: 'a subscription exists when queried but has only 1 subscriber'
-            def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
+            mockCpsQueryService.queryDataNodes(
+                'NCMP-Admin', 'cm-data-subscriptions',
+                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y'),
+                OMIT_DESCENDANTS) >> [new DataNode(leaves: ['xpath': '/x/y', 'subscriptionIds': ['sub-1']])]
+        and: 'the #scenario'
             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
+                CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted('ncmp-datastore:passthrough-running', 'ch-1'),
+                DIRECT_CHILDREN_ONLY) >> [new DataNode(childDataNodes: listOfChildNodes)]
         when: 'that last ongoing subscription is removed'
-            objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
+            objectUnderTest.removeCmSubscription(PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
         then: 'the subscription with empty subscriber list is removed'
             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters/filter[@xpath=\'/x/y\']',
                 _)
+        and: 'method call to delete the cm handle is called the correct number of times'
+            numberOfCallsToDeleteCmHandle * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
+                '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']',
+                _)
+        where:
+            scenario                                                          | listOfChildNodes || numberOfCallsToDeleteCmHandle
+            'cm handle in same datastore is used for other subscriptions'     | [new DataNode()] || 0
+            'cm handle in same datastore is NOT used for other subscriptions' | []               || 1
     }
-}
\ No newline at end of file
+
+    def 'Get all nodes for subscription id'() {
+        given: 'the query service returns nodes for subscription id'
+            def expectedDataNode = new DataNode(xpath: '/some/xpath')
+            def queryServiceResponse = [expectedDataNode].asCollection()
+            1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', '//filter/subscriptionIds[text()=\'some-id\']', OMIT_DESCENDANTS) >> queryServiceResponse
+        when: 'retrieving all nodes for subscription id'
+            def result = objectUnderTest.getAllNodesForSubscriptionId('some-id')
+        then: 'the result returns correct number of datanodes'
+            assert result.size() == 1
+        and: 'the attribute of the datanode is as expected'
+            assert result.iterator().next().xpath == expectedDataNode.xpath
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy
new file mode 100644 (file)
index 0000000..fd76abb
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications 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.impl.data
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.events.EventsPublisher
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest
+import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException
+import org.onap.cps.ncmp.config.CpsApplicationContext
+import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent
+import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
+import org.onap.cps.ncmp.impl.dmi.DmiOperationsBaseSpec
+import org.onap.cps.ncmp.impl.dmi.DmiProperties
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
+import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.utils.JsonObjectMapper
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.test.context.ContextConfiguration
+import reactor.core.publisher.Mono
+import spock.lang.Shared
+
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
+import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
+import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
+import static org.onap.cps.ncmp.api.data.models.OperationType.READ
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA
+import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent
+
+@SpringBootTest
+@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations, PolicyExecutor])
+class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
+
+    def NO_TOPIC = null
+    def NO_REQUEST_ID = null
+    def NO_AUTH_HEADER = null
+
+    @Shared
+    def OPTIONS_PARAM = '(a=1,b=2)'
+
+    @SpringBean
+    JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
+
+    @Autowired
+    DmiDataOperations objectUnderTest
+
+    @SpringBean
+    EventsPublisher eventsPublisher = Stub()
+
+    @SpringBean
+    PolicyExecutor policyExecutor = Mock()
+
+    @SpringBean
+    AlternateIdMatcher alternateIdMatcher = Mock()
+
+    def 'call get resource data for #expectedDataStore from DMI without topic #scenario.'() {
+        given: 'a cm handle for #cmHandleId'
+            mockYangModelCmHandleRetrieval(dmiProperties)
+        and: 'a positive response from DMI service when it is called with the expected parameters'
+            def responseFromDmi = Mono.just(new ResponseEntity<Object>('{some-key:some-value}', HttpStatus.OK))
+            def expectedUrlTemplateWithVariables = getExpectedUrlTemplateWithVariables(expectedOptions, expectedDataStore)
+            def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}'
+            mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrlTemplateWithVariables, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi
+        when: 'get resource data is invoked'
+            def cmResourceAddress = new CmResourceAddress(expectedDataStore.datastoreName, cmHandleId, resourceIdentifier)
+            alternateIdMatcher.getCmHandleId(cmHandleId) >> cmHandleId
+            def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, expectedOptions, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER).block()
+        then: 'the result is the response from the DMI service'
+            assert result.body == '{some-key:some-value}'
+            assert result.statusCode.'2xxSuccessful'
+        where: 'the following parameters are used'
+            scenario                               | dmiProperties               || expectedDataStore       | expectedOptions | expectedProperties
+            'without properties'                   | []                          || PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM   | '{}'
+            'with properties'                      | [yangModelCmHandleProperty] || PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM   | '{"prop1":"val1"}'
+            'null options'                         | [yangModelCmHandleProperty] || PASSTHROUGH_OPERATIONAL | null            | '{"prop1":"val1"}'
+            'empty options'                        | [yangModelCmHandleProperty] || PASSTHROUGH_OPERATIONAL | ''              | '{"prop1":"val1"}'
+            'datastore running without properties' | []                          || PASSTHROUGH_RUNNING     | OPTIONS_PARAM   | '{}'
+            'datastore running with properties'    | [yangModelCmHandleProperty] || PASSTHROUGH_RUNNING     | OPTIONS_PARAM   | '{"prop1":"val1"}'
+    }
+
+    def 'Execute (async) data operation from DMI service.'() {
+        given: 'collection of yang model cm Handles and data operation request'
+            mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty])
+            def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
+            def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class)
+            dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId]
+        and: 'a positive response from DMI service when it is called with valid request parameters'
+            def responseFromDmi = Mono.just(new ResponseEntity<Object>(HttpStatus.ACCEPTED))
+            def expectedUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/data?requestId={requestId}&topic={topic}', ['requestId': 'requestId', 'topic': 'my-topic-name'])
+            def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}'
+            mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrlTemplateWithVariables, _, READ, NO_AUTH_HEADER) >> responseFromDmi
+        when: 'get resource data for group of cm handles is invoked'
+            objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
+        then: 'the post operation was called with the expected URL and JSON request body'
+            1 * mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrlTemplateWithVariables, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER)
+    }
+
+    def 'Execute (async) data operation from DMI service with Exception.'() {
+        given: 'collection of yang model cm Handles and data operation request'
+            mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty])
+            def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
+            def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class)
+            dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId]
+        and: 'the published cloud event will be captured'
+            def actualDataOperationCloudEvent = null
+            eventsPublisher.publishCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] }
+        and: 'a DMI client request exception is thrown when DMI service is called'
+            mockDmiRestClient.asynchronousPostOperationWithJsonData(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) }
+        when: 'attempt to get resource data for group of cm handles is invoked'
+            objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'my-request-id', NO_AUTH_HEADER)
+        then: 'the event contains the expected error details'
+            def eventDataValue = extractDataValue(actualDataOperationCloudEvent)
+            assert eventDataValue.statusCode == '108'
+            assert eventDataValue.statusMessage == UNKNOWN_ERROR.message
+        and: 'the event contains the correct operation details'
+            assert eventDataValue.operationId == dataOperationRequest.dataOperationDefinitions[0].operationId
+            assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleIds
+    }
+
+    def 'call get all resource data.'() {
+        given: 'the system returns a cm handle with a sample property and sample module set tag'
+            mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], 'my-module-set-tag')
+        and: 'a positive response from DMI service when it is called with the expected parameters'
+            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
+            def expectedTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/data/ds/{datastore}?resourceIdentifier={resourceIdentifier}', ['resourceIdentifier': '/', 'datastore': 'ncmp-datastore:passthrough-operational', 'cmHandleId': cmHandleId])
+            def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}'
+            mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedTemplateWithVariables, expectedJson, READ, null) >> responseFromDmi
+        when: 'get resource data is invoked'
+            def result = objectUnderTest.getAllResourceDataFromDmi(cmHandleId, NO_REQUEST_ID)
+        then: 'the result is the response from the DMI service'
+            assert result == responseFromDmi
+    }
+
+    def 'Write data for pass-through:running datastore in DMI.'() {
+        given: 'a cm handle for #cmHandleId'
+            mockYangModelCmHandleRetrieval([yangModelCmHandleProperty])
+            alternateIdMatcher.getCmHandleId(cmHandleId) >> cmHandleId
+        and: 'a positive response from DMI service when it is called with the expected parameters'
+            def expectedUrlTemplateParameters = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/data/ds/{datastore}?resourceIdentifier={resourceIdentifier}', ['resourceIdentifier': resourceIdentifier, 'datastore': 'ncmp-datastore:passthrough-running', 'cmHandleId': cmHandleId])
+            def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}'
+            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
+            mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrlTemplateParameters, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi
+        when: 'write resource method is invoked'
+            def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER)
+        then: 'the result is the response from the DMI service'
+            assert result == responseFromDmi
+        and: 'the permission was checked with the policy executor'
+            1 * policyExecutor.checkPermission(_, operation, NO_AUTH_HEADER, resourceIdentifier, 'requestData' )
+        where: 'the following operation is performed'
+            operation || expectedOperationInUrl
+            CREATE    || 'create'
+            UPDATE    || 'update'
+            DELETE    || 'delete'
+            PATCH     || 'patch'
+    }
+
+    def 'State Ready validation'() {
+        given: 'a yang model cm handle'
+            populateYangModelCmHandle([] ,'')
+        when: 'Validating State of #cmHandleState'
+            def caughtException = null
+            try {
+                objectUnderTest.validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState)
+            } catch (Exception e) {
+                caughtException = e
+            }
+        then: 'only when not ready a exception is thrown'
+            if (expecteException) {
+                assert caughtException.details.contains('not in READY state')
+            } else {
+                assert caughtException == null
+            }
+        where: 'the following states are used'
+            cmHandleState         || expecteException
+            CmHandleState.READY   || false
+            CmHandleState.ADVISED || true
+    }
+
+    def extractDataValue(actualDataOperationCloudEvent) {
+        return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent).data.responses[0]
+    }
+
+    def getExpectedUrlTemplateWithVariables(expectedOptions, expectedDataStore) {
+        def includeOptions = !(expectedOptions == null || expectedOptions.trim().isEmpty())
+        def expectedUrlTemplate = 'myServiceName/dmi/v1/ch/{cmHandleId}/data/ds/{datastore}?resourceIdentifier={resourceIdentifier}' + (includeOptions ? '&options={options}' : '')
+        def urlVariables = ['resourceIdentifier': resourceIdentifier, 'datastore': expectedDataStore.datastoreName, 'cmHandleId': cmHandleId] + (includeOptions ? ['options': expectedOptions] : [:])
+        return new UrlTemplateParameters(expectedUrlTemplate, urlVariables)
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy
new file mode 100644 (file)
index 0000000..314b761
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.data
+
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.events.EventsPublisher
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress
+import org.onap.cps.ncmp.config.CpsApplicationContext
+import org.onap.cps.ncmp.impl.dmi.DmiProperties
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+import org.onap.cps.spi.model.DataNode
+import org.spockframework.spring.SpringBean
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.context.ApplicationContext
+import org.springframework.test.context.ContextConfiguration
+import reactor.core.publisher.Mono
+import spock.lang.Specification
+
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+
+@SpringBootTest
+@ContextConfiguration(classes = [CpsApplicationContext])
+class NcmpCachedResourceRequestHandlerSpec extends Specification {
+
+    def cpsDataService = Mock(CpsDataService)
+    def networkCmProxyQueryService= Mock(NetworkCmProxyQueryService)
+
+    @SpringBean
+    AlternateIdMatcher alternateIdMatcher = Mock()
+
+    @SpringBean
+    ApplicationContext applicationContext = Mock()
+
+    def objectUnderTest = new NcmpCachedResourceRequestHandler(cpsDataService, networkCmProxyQueryService)
+
+    def 'Execute a request with include descendants = #includeDescendants.'() {
+        when: 'executing a request'
+            objectUnderTest.executeRequest('ch-1', 'resource', includeDescendants)
+        then: 'it is delegated to the ncmp query service with the correct option'
+            1 * networkCmProxyQueryService.queryResourceDataOperational('ch-1','resource', expectedFetchDescendantsOption)
+        where: 'the following options are used'
+            includeDescendants || expectedFetchDescendantsOption
+            true               || INCLUDE_ALL_DESCENDANTS
+            false              || OMIT_DESCENDANTS
+    }
+
+    def 'Get resource data.'() {
+        given: 'the data service returns 2 nodes for the given resource address'
+            def cmResourceAddress = new CmResourceAddress('datastore','ch-1','resource')
+            def dataNode1 = new DataNode(xpath:'p1')
+            def dataNode2 = new DataNode(xpath:'p2')
+            cpsDataService.getDataNodes('datastore','ch-1','resource',OMIT_DESCENDANTS) >> [dataNode1, dataNode2]
+        when: 'getting the resource data'
+            alternateIdMatcher.getCmHandleId('ch-1') >> 'ch-1'
+            def result = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', 'topic', 'request id', false, 'authorization')
+        then: 'the result is a "Mono" holding just the first data node'
+            assert result instanceof Mono
+            assert result.block() == dataNode1
+    }
+
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.controller.handlers
+package org.onap.cps.ncmp.impl.data
 
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException
-import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException
-import org.onap.cps.ncmp.api.models.DataOperationDefinition
-import org.onap.cps.ncmp.api.models.DataOperationRequest
-import org.onap.cps.ncmp.api.models.CmResourceAddress
-import org.onap.cps.ncmp.rest.exceptions.OperationNotSupportedException
-import org.onap.cps.ncmp.rest.exceptions.PayloadTooLargeException
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
+import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException
+import org.onap.cps.ncmp.api.data.exceptions.InvalidOperationException
+import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress
+import org.onap.cps.ncmp.api.data.models.DataOperationDefinition
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest
+import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException
+import org.springframework.http.ResponseEntity
+import reactor.core.publisher.Mono
 import spock.lang.Specification
-import spock.util.concurrent.PollingConditions
+
+import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT
 
 class NcmpDatastoreRequestHandlerSpec extends Specification {
 
-    def spiedCpsNcmpTaskExecutor = Spy(CpsNcmpTaskExecutor)
-    def mockNetworkCmProxyDataService = Mock(NetworkCmProxyDataService)
+    def dmiDataOperations = Mock(DmiDataOperations)
 
-    def objectUnderTest = new NcmpPassthroughResourceRequestHandler(spiedCpsNcmpTaskExecutor, mockNetworkCmProxyDataService)
+    def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations)
 
+    def NO_TOPIC = null
     def NO_AUTH_HEADER = null
 
-    def setup() {
-        objectUnderTest.timeOutInMilliSeconds = 100
-    }
-
     def 'Attempt to execute async get request with #scenario.'() {
         given: 'notification feature is turned on/off'
             objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled
-        and: 'a flag to track the network service call'
-            def networkServiceMethodCalled = false
         and: 'a CM resource address'
             def cmResourceAddress = new CmResourceAddress('ds', 'ch1', 'resource1')
-        and: 'the (mocked) service will use the flag to indicate if it is called'
-            mockNetworkCmProxyDataService.getResourceDataForCmHandle(cmResourceAddress, 'options', _, _, NO_AUTH_HEADER) >>
-                { networkServiceMethodCalled = true }
+        and: 'the (mocked) service when called with the correct parameters (with or without topic) returns a response from dmi'
+            def dmiResponse = Mono.justOrEmpty(new ResponseEntity('dmi response',I_AM_A_TEAPOT))
+            dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', NO_TOPIC, _, NO_AUTH_HEADER) >> dmiResponse
+            dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', topic, _, NO_AUTH_HEADER) >> dmiResponse
         when: 'get request is executed with topic = #topic'
-            objectUnderTest.executeRequest(cmResourceAddress, 'options', topic, false, NO_AUTH_HEADER)
-        then: 'the task is executed in an async fashion or not'
-            expectedCalls * spiedCpsNcmpTaskExecutor.executeTask(*_)
-        and: 'the service request is invoked'
-            new PollingConditions().within(1) {
-                assert networkServiceMethodCalled == true
+            def response = objectUnderTest.executeRequest(cmResourceAddress, 'options', topic, false, NO_AUTH_HEADER)
+        then: 'a successful result with/without request id is returned'
+            if (expectSynchronousResponse) {
+                assert response == 'dmi response'
+            } else { // expect request id in a map
+                assert response.keySet()[0] == 'requestId'
             }
         where: 'the following parameters are used'
-            scenario                   | notificationFeatureEnabled | topic   || expectedCalls
-            'feature on, valid topic'  | true                       | 'valid' || 1
-            'feature on, no topic'     | true                       | null    || 0
-            'feature off, valid topic' | false                      | 'valid' || 0
-            'feature off, no topic'    | false                      | null    || 0
+            scenario                   | notificationFeatureEnabled | topic   || expectSynchronousResponse
+            'feature on, valid topic'  | true                       | 'valid' || false
+            'feature on, no topic'     | true                       | null    || true
+            'feature off, valid topic' | false                      | 'valid' || true
+            'feature off, no topic'    | false                      | null    || true
     }
 
     def 'Attempt to execute async data operation request with feature #scenario.'() {
         given: 'a extended request handler that supports bulk requests'
-           def objectUnderTest = new NcmpPassthroughResourceRequestHandler(spiedCpsNcmpTaskExecutor, mockNetworkCmProxyDataService)
+           def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations)
         and: 'notification feature is turned on/off'
             objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled
         when: 'data operation request is executed'
-            objectUnderTest.executeRequest('someTopic', new DataOperationRequest(), NO_AUTH_HEADER)
+            def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: ['ch'])
+            def result = objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER)
         then: 'the task is executed in an async fashion or not'
-            expectedCalls * spiedCpsNcmpTaskExecutor.executeTaskWithErrorHandling(*_)
+            expectedCalls * dmiDataOperations.requestResourceDataFromDmi('someTopic', _, _, NO_AUTH_HEADER)
+        and:
+            result.keySet()[0] == expectedKeyInMap
         where: 'the following parameters are used'
-            scenario | notificationFeatureEnabled || expectedCalls
-            'on'     | true                       || 1
-            'off'    | false                      || 0
-    }
-
-    def 'Execute async data operation request with datastore #datastore.'() {
-        given: 'notification feature is turned on'
-            objectUnderTest.notificationFeatureEnabled = true
-        and: 'a data operation request with datastore: #datastore'
-            def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore)
-            def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition])
-        and: ' a flag to track the network service call'
-            def networkServiceMethodCalled = false
-        and: 'the (mocked) service will use the flag to indicate it is called'
-            mockNetworkCmProxyDataService.executeDataOperationForCmHandles('myTopic', dataOperationRequest, _, NO_AUTH_HEADER) >> {
-                networkServiceMethodCalled = true
-            }
-        when: 'data operation request is executed'
-            objectUnderTest.executeRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER)
-        then: 'the task is executed in an async fashion'
-            1 * spiedCpsNcmpTaskExecutor.executeTaskWithErrorHandling(*_)
-        and: 'the network service is invoked'
-            new PollingConditions().within(1) {
-                assert networkServiceMethodCalled == true
-            }
-        where: 'the following datastores are used'
-            datastore << ['ncmp-datastore:passthrough-running', 'ncmp-datastore:passthrough-operational']
+            scenario | notificationFeatureEnabled || expectedCalls || expectedKeyInMap
+            'on'     | true                       || 1             || 'requestId'
+            'off'    | false                      || 0             || 'status'
     }
 
     def 'Attempt to execute async data operation request with error #scenario'() {
@@ -115,7 +90,7 @@ class NcmpDatastoreRequestHandlerSpec extends Specification {
             def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore)
         when: 'data operation request is executed'
             def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition])
-            objectUnderTest.executeRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER)
+            objectUnderTest.executeAsynchronousRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER)
         then: 'the correct error is thrown'
             def thrown = thrown(InvalidDatastoreException)
             assert thrown.message.contains(expectedErrorMessage)
@@ -129,7 +104,7 @@ class NcmpDatastoreRequestHandlerSpec extends Specification {
         given: 'a data operation definition with operation: #operation'
             def dataOperationDefinition = new DataOperationDefinition(operation: operation, datastore: 'ncmp-datastore:passthrough-running')
         when: 'data operation request is executed'
-            objectUnderTest.executeRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER)
+            objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER)
         then: 'the expected type of exception is thrown'
             thrown(expectedException)
         where: 'the following operations are used'
@@ -143,11 +118,11 @@ class NcmpDatastoreRequestHandlerSpec extends Specification {
 
     def 'Attempt to execute async data operation request with too many cm handles.'() {
         given: 'a data operation definition with too many cm handles'
-            def tooMany = objectUnderTest.MAXIMUM_CM_HANDLES_PER_OPERATION+1
+            def tooMany = objectUnderTest.MAXIMUM_CM_HANDLES_PER_OPERATION + 1
             def cmHandleIds = new String[tooMany]
             def dataOperationDefinition = new DataOperationDefinition(operationId: 'abc', operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: cmHandleIds)
         when: 'data operation request is executed'
-            objectUnderTest.executeRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER)
+            objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER)
         then: 'a payload too large exception is thrown'
             def exceptionThrown = thrown(PayloadTooLargeException)
         and: 'the error message contains the offending number of cm handles'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy
new file mode 100644 (file)
index 0000000..5f83ad5
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2021 Pantheon.tech
+ *  Modifications Copyright (C) 2021-2022 Bell Canada
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  ================================================================================
+ *  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.impl.data
+
+
+import org.onap.cps.ncmp.api.data.models.CmResourceAddress
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+import org.onap.cps.spi.model.DataNode
+import reactor.core.publisher.Mono
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
+
+class NetworkCmProxyFacadeSpec extends Specification {
+
+    def mockDmiDataOperations = Mock(DmiDataOperations)
+    def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler)
+    def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler)
+    def mockAlternateIdMatcher =  Mock(AlternateIdMatcher)
+
+    def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations, mockAlternateIdMatcher)
+
+    def NO_TOPIC = null
+
+    def 'Execute Data Operation for CM Handles (delegation).'() {
+        given: 'a data operation request'
+            def dataOperationRequest = Mock(DataOperationRequest)
+        and: ' a response from the (mocked) pass-through request handler for the given parameters'
+            def responseFromHandler = [attr:'value']
+            mockNcmpPassthroughResourceRequestHandler.executeAsynchronousRequest('topic', dataOperationRequest, 'authorization') >> responseFromHandler
+        expect: 'the response form the handler'
+            assert objectUnderTest.executeDataOperationForCmHandles('topic', dataOperationRequest, 'authorization') == responseFromHandler
+    }
+
+    def 'Query Resource Data for cm handle (delegation).'() {
+        given: 'a response from the (mocked) cached data handler for the given parameters'
+            def responseFromHandler = [Mock(DataNode)]
+            mockNcmpCachedResourceRequestHandler.executeRequest('ch-1', 'some cps path', true) >> responseFromHandler
+        expect: 'the response form the handler'
+            assert objectUnderTest.queryResourceDataForCmHandle('ch-1','some cps path',true) == responseFromHandler
+    }
+
+    def 'Choosing Data Request Handler.'() {
+        expect: '(a mock of) #expectedHandler'
+            assert objectUnderTest.getNcmpDatastoreRequestHandler(datastore.datastoreName).class.name.startsWith(expectedHandler.name)
+        where:
+            datastore               || expectedHandler
+            OPERATIONAL             || NcmpCachedResourceRequestHandler.class
+            PASSTHROUGH_RUNNING     || NcmpPassthroughResourceRequestHandler.class
+            PASSTHROUGH_OPERATIONAL || NcmpPassthroughResourceRequestHandler.class
+    }
+
+    def 'Write resource data for pass-through running from DMI using POST (delegation).'() {
+        when: 'write resource data is called'
+            objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'testResourceId', CREATE,
+                '{some-json}', 'application/json', null)
+        then: 'DMI called with correct data'
+            1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', CREATE, '{some-json}', 'application/json', null)
+    }
+
+    def 'Get resource data from DMI (delegation).'() {
+        given: 'a cm resource address for datastore operational'
+            def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id')
+        and: 'get resource data from DMI is called'
+            mockAlternateIdMatcher.getCmHandleId('some CM Handle') >> 'some CM Handle'
+            mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >>
+                    Mono.just('dmi response')
+        when: 'get resource data operational for the given cm resource address is called'
+            def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization').block()
+        then: 'DMI returns a json response'
+            assert response == 'dmi response'
+    }
+
+    def 'Update resource data for pass-through running from dmi (delegation).'() {
+        when: 'get resource data is called'
+            objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'testResourceId', UPDATE,
+                '{some-json}', 'application/json', 'authorization')
+        then: 'DMI called with correct data'
+            1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', UPDATE, '{some-json}', 'application/json', 'authorization')
+    }
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+package org.onap.cps.ncmp.impl.data
 
 import org.onap.cps.api.CpsQueryService
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
+
 class NetworkCmProxyQueryServiceImplSpec extends Specification {
 
     def mockCpsQueryService = Mock(CpsQueryService)
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.async
+package org.onap.cps.ncmp.impl.data.async
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.apache.kafka.clients.consumer.KafkaConsumer
 import org.apache.kafka.common.serialization.StringDeserializer
 import org.mapstruct.factory.Mappers
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
 import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent
 import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.test.annotation.DirtiesContext
 import org.testcontainers.spock.Testcontainers
+
 import java.time.Duration
 
 @SpringBootTest(classes = [EventsPublisher, AsyncRestRequestResponseEventConsumer, ObjectMapper, JsonObjectMapper])
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.async
+package org.onap.cps.ncmp.impl.data.async
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
+import io.cloudevents.core.builder.CloudEventBuilder
 import io.cloudevents.kafka.CloudEventDeserializer
 import io.cloudevents.kafka.CloudEventSerializer
 import io.cloudevents.kafka.impl.KafkaHeaders
-import io.cloudevents.core.builder.CloudEventBuilder
 import org.apache.kafka.clients.consumer.ConsumerRecord
 import org.apache.kafka.clients.consumer.KafkaConsumer
 import org.apache.kafka.common.header.internals.RecordHeaders
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
 import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
@@ -40,9 +40,10 @@ import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.kafka.listener.adapter.RecordFilterStrategy
 import org.springframework.test.annotation.DirtiesContext
 import org.testcontainers.spock.Testcontainers
+
 import java.time.Duration
 
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
+import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent
 
 @SpringBootTest(classes = [EventsPublisher, DataOperationEventConsumer, RecordFilterStrategies, JsonObjectMapper, ObjectMapper])
 @Testcontainers
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.async
+package org.onap.cps.ncmp.impl.data.async
 
 import io.cloudevents.core.builder.CloudEventBuilder
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.impl.config.kafka.KafkaConfig
-import org.onap.cps.ncmp.api.kafka.ConsumerBaseSpec
+import org.onap.cps.ncmp.config.KafkaConfig
 import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent
+import org.onap.cps.ncmp.utils.events.ConsumerBaseSpec
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Value
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -32,6 +32,7 @@ import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.test.annotation.DirtiesContext
 import org.testcontainers.spock.Testcontainers
 import spock.util.concurrent.PollingConditions
+
 import java.util.concurrent.TimeUnit
 
 @SpringBootTest(classes =[DataOperationEventConsumer, AsyncRestRequestResponseEventConsumer, RecordFilterStrategies, KafkaConfig])
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.async
+package org.onap.cps.ncmp.impl.data.async
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.core.builder.CloudEventBuilder
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.impl.config.kafka.KafkaConfig
-import org.onap.cps.ncmp.api.kafka.ConsumerBaseSpec
+import org.onap.cps.ncmp.config.KafkaConfig
 import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent
 import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent
 import org.onap.cps.ncmp.events.async1_0_0.Data
 import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent
 import org.onap.cps.ncmp.events.async1_0_0.Response
+import org.onap.cps.ncmp.utils.events.ConsumerBaseSpec
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy
new file mode 100644 (file)
index 0000000..c859bb0
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.data.policyexecutor
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig
+import org.onap.cps.ncmp.impl.policyexecutor.PolicyExecutorWebClientConfiguration
+import org.onap.cps.ncmp.utils.WebClientBuilderTestConfig
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [ObjectMapper, PolicyExecutor, PolicyExecutorWebClientConfiguration,  PolicyExecutorHttpClientConfig, WebClientBuilderTestConfig ])
+class PolicyExecutorConfigurationSpec extends Specification {
+
+    @Autowired
+    PolicyExecutor objectUnderTest
+
+    def 'Policy executor configuration properties.'() {
+        expect: 'properties used from application.yml'
+            assert objectUnderTest.enabled
+            assert objectUnderTest.serverAddress == 'http://localhost'
+            assert objectUnderTest.serverPort == '8785'
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy
new file mode 100644 (file)
index 0000000..63a915a
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.data.policyexecutor
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.exceptions.NcmpException
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.reactive.function.client.WebClient
+import reactor.core.publisher.Mono
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
+import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
+import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
+
+class PolicyExecutorSpec extends Specification {
+
+    def mockWebClient = Mock(WebClient)
+    def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec)
+    def mockResponseSpec = Mock(WebClient.ResponseSpec)
+    def spiedObjectMapper = Spy(ObjectMapper)
+
+    PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient, spiedObjectMapper)
+
+    def logAppender = Spy(ListAppender<ILoggingEvent>)
+
+    def someValidJson = '{"Hello":"World"}'
+
+    def setup() {
+        setupLogger()
+        objectUnderTest.enabled = true
+        mockWebClient.post() >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.header(*_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.body(*_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.retrieve() >> mockResponseSpec
+    }
+
+    def cleanup() {
+        ((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders()
+    }
+
+    def 'Permission check with allow response.'() {
+        given: 'allow response'
+            mockResponse([decision:'allow'], HttpStatus.OK)
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson)
+        then: 'system logs the operation is allowed'
+            assert getLogEntry(2) == 'Policy Executor allows the operation'
+        and: 'no exception occurs'
+            noExceptionThrown()
+        where: 'all write operations are tested'
+            operationType << [ CREATE, DELETE, PATCH, UPDATE ]
+    }
+
+    def 'Permission check with other response (not allowed).'() {
+        given: 'other response'
+            mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK)
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson)
+        then: 'Policy Executor exception is thrown'
+            def thrownException = thrown(PolicyExecutorException)
+            assert thrownException.message == 'Policy Executor did not allow request. Decision #123 : other'
+            assert thrownException.details == 'I dont like Mondays'
+    }
+
+    def 'Permission check with non 2xx response.'() {
+        given: 'other response'
+            mockResponse([], HttpStatus.I_AM_A_TEAPOT)
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson)
+        then: 'Server Ncmp exception is thrown'
+            def thrownException = thrown(ServerNcmpException)
+            assert thrownException.message == 'Policy Executor invocation failed'
+            assert thrownException.details == 'HTTP status code: 418'
+    }
+
+    def 'Permission check with invalid response from Policy Executor.'() {
+        given: 'invalid response from Policy executor'
+            mockResponseSpec.toEntity(*_) >> invalidResponse
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials','my resource',someValidJson)
+        then: 'system logs the expected message'
+            assert getLogEntry(1) == expectedMessage
+        where: 'following invalid responses are received'
+            invalidResponse                                        || expectedMessage
+            Mono.empty()                                           || 'No valid response from policy, ignored'
+            Mono.just(new ResponseEntity<>(null, HttpStatus.OK))   || 'No valid response body from policy, ignored'
+    }
+
+    def 'Permission check with an invalid change request json.'() {
+        when: 'permission is checked for an invalid change request'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', 'invalid json string')
+        then: 'an ncmp exception thrown'
+            def ncmpException = thrown(NcmpException)
+            ncmpException.message == 'Cannot convert Change Request data to Object'
+            ncmpException.details.contains('invalid json string')
+    }
+
+    def 'Permission check feature disabled.'() {
+        given: 'feature is disabled'
+            objectUnderTest.enabled = false
+        when: 'permission is checked for an operation'
+            objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson)
+        then: 'system logs that the feature not enabled'
+            assert getLogEntry(0) == 'Policy Executor Enabled: false'
+    }
+
+    def mockResponse(mockResponseAsMap, httpStatus) {
+        JsonNode jsonNode = spiedObjectMapper.readTree(spiedObjectMapper.writeValueAsString(mockResponseAsMap))
+        def mono = Mono.just(new ResponseEntity<>(jsonNode, httpStatus))
+        mockResponseSpec.toEntity(*_) >> mono
+    }
+
+    def setupLogger() {
+        def logger = LoggerFactory.getLogger(PolicyExecutor)
+        logger.setLevel(Level.TRACE)
+        logger.addAppender(logAppender)
+        logAppender.start()
+    }
+
+    def getLogEntry(index) {
+        logAppender.list[index].formattedMessage
+    }
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils.data.operation
+package org.onap.cps.ncmp.impl.data.utils
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
@@ -26,27 +26,28 @@ import io.cloudevents.kafka.CloudEventDeserializer
 import io.cloudevents.kafka.impl.KafkaHeaders
 import org.apache.kafka.clients.consumer.KafkaConsumer
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.NcmpResponseStatus
-import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
-import org.onap.cps.ncmp.api.models.DataOperationRequest
+import org.onap.cps.ncmp.api.data.models.DataOperationRequest
+import org.onap.cps.ncmp.api.data.models.OperationType
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
+import org.onap.cps.ncmp.config.CpsApplicationContext
 import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent
+import org.onap.cps.ncmp.impl.data.models.DmiDataOperation
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
-import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.test.context.ContextConfiguration
+import org.springframework.util.LinkedMultiValueMap
 
 import java.time.Duration
-import java.util.concurrent.TimeoutException
 
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY
+import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent
 
-@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, ObjectMapper])
-class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
+@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext])
+class DmiDataOperationsHelperSpec extends MessagingBaseSpec {
 
     def static clientTopic = 'my-topic-name'
     def static dataOperationType = 'org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent'
@@ -57,9 +58,6 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
     @SpringBean
     EventsPublisher eventPublisher = new EventsPublisher<CloudEvent>(legacyEventKafkaTemplate, cloudEventKafkaTemplate)
 
-    @Autowired
-    ObjectMapper objectMapper
-
     def 'Process per data operation request with #serviceName.'() {
         given: 'data operation request with 3 operations'
             def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
@@ -67,7 +65,7 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
         and: '4 known cm handles: ch1-dmi1, ch2-dmi1, ch3-dmi2, ch4-dmi2'
             def yangModelCmHandles = getYangModelCmHandles()
         when: 'data operation request is processed'
-            def operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles)
+            def operationsOutPerDmiServiceName = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles)
         and: 'converted to a json node'
             def dmiDataOperationRequestBody = jsonObjectMapper.asJsonString(operationsOutPerDmiServiceName.get(serviceName))
             def dmiDataOperationRequestBodyAsJsonNode = jsonObjectMapper.convertToJsonNode(dmiDataOperationRequestBody).get(operationIndex)
@@ -90,6 +88,23 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
             'dmi2'      | 2              || 'operational-15'    | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2']
     }
 
+    def 'Process one data operation request with #serviceName and Module Set Tag set.'() {
+        given: 'data operation request'
+            def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
+            def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class)
+        and: '1 known cm handles: ch1-dmi1'
+            def yangModelCmHandles = getYangModelCmHandlesForOneCmHandle()
+        when: 'data operation request is processed'
+            def operationsOutPerDmiServiceName = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles)
+        and: 'converted to a json node'
+            def dmiDataOperationRequestBody = operationsOutPerDmiServiceName['dmi1']
+            def cmHandlesInRequestBody = dmiDataOperationRequestBody[0].cmHandles
+        then: 'it contains the correct operation details'
+            assert cmHandlesInRequestBody.size() == 1
+            assert cmHandlesInRequestBody[0].id == 'ch1-dmi1'
+            assert cmHandlesInRequestBody[0].moduleSetTag == 'module-set-tag1'
+    }
+
     def 'Process per data operation request with non-ready, non-existing cm handle and publish event to client specified topic'() {
         given: 'consumer subscribing to client topic'
             def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test-1', CloudEventDeserializer))
@@ -98,7 +113,7 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
             def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
             def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class)
         when: 'data operation request is processed'
-            ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, yangModelCmHandles)
+            DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, yangModelCmHandles)
         and: 'subscribed client specified topic is polled and first record is selected'
             def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).last()
         then: 'verify cloud compliant headers'
@@ -118,34 +133,10 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
             jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) == dataOperationResponseEventJson
     }
 
-    def 'Publish error response for entire data operations request when async task fails'() {
-        given: 'consumer subscribing to client topic'
-            def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties(consumerGroupId, CloudEventDeserializer))
-            cloudEventKafkaConsumer.subscribe([clientTopic])
-        and: 'data operation request having non-ready and non-existing cm handle ids'
-            def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
-            def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class)
-        when: 'an error occurs for the entire data operations request'
-            ResourceDataOperationRequestUtils.handleAsyncTaskCompletionForDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, exceptionThrown)
-        and: 'subscribed client specified topic is polled and first record is selected'
-            def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).last()
-            def dataOperationResponseEvent = toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class)
-        then: 'data operation response event response size is 3'
-            dataOperationResponseEvent.data.responses.size() == 3
-        and: 'all 3 have the expected error code'
-            dataOperationResponseEvent.data.responses.each {
-                assert it.statusCode == errorReportedToClientTopic.code
-            }
-        where:
-            scenario             | exceptionThrown        | consumerGroupId || errorReportedToClientTopic
-            'task timed out'     | new TimeoutException() | 'test-2'        || NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING
-            'unspecified error'  | new RuntimeException() | 'test-3'        || NcmpResponseStatus.UNKNOWN_ERROR
-    }
-
     static def getYangModelCmHandles() {
         def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
-        def readyState = new CompositeStateBuilder().withCmHandleState(CmHandleState.READY).withLastUpdatedTimeNow().build()
-        def advisedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).withLastUpdatedTimeNow().build()
+        def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build()
+        def advisedState = new CompositeStateBuilder().withCmHandleState(ADVISED).withLastUpdatedTimeNow().build()
         return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState),
                 new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState),
                 new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState),
@@ -156,4 +147,19 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
                 new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState)
         ]
     }
+
+    static def getYangModelCmHandlesForOneCmHandle() {
+        def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
+        def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build()
+        return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', moduleSetTag: 'module-set-tag1', dmiProperties: dmiProperties, compositeState: readyState)]
+    }
+
+    def mockAndPopulateErrorMap(errorReportedToClientTopic) {
+        def dmiDataOperation = DmiDataOperation.builder().operation(OperationType.fromOperationName('read'))
+                .operationId('some-op-id').datastore('ncmp-datastore:passthrough-operational')
+                .options('some-option').resourceIdentifier('some-resource-identifier').build()
+        def cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>()
+        cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(errorReportedToClientTopic, ['some-cm-handle-id']))
+        return cmHandleIdsPerResponseCodesPerOperation
+    }
 }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy
new file mode 100644 (file)
index 0000000..74bd048
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs
+
+import org.onap.cps.ncmp.impl.dmi.DmiProperties
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
+import reactor.core.publisher.Mono
+import spock.lang.Specification
+
+class DataJobResultServiceImplSpec extends Specification {
+
+    def mockDmiRestClient = Mock(DmiRestClient)
+    def mockDmiProperties = Mock(DmiProperties)
+    def objectUnderTest = new DataJobResultServiceImpl(mockDmiRestClient, mockDmiProperties)
+
+    def setup() {
+        mockDmiProperties.dmiBasePath >> 'dmi'
+    }
+
+    def 'Retrieve data job result.'() {
+        given: 'the required parameters for querying'
+            def dmiServiceName = 'some-dmi-service'
+            def dataProducerJobId = 'some-data-producer-job-id'
+            def dataProducerId = 'some-data-producer-id'
+            def authorization = 'my authorization header'
+            def destination = 'some-destination'
+            def urlParams = new UrlTemplateParameters('some-dmi-service/dmi/v1/cmwriteJob/dataProducer/{dataProducerId}/dataProducerJob/{dataProducerJobId}/result?destination={destination}', ['dataProducerJobId':'some-data-producer-job-id', 'dataProducerId':'some-data-producer-id', 'destination': 'some-destination'])
+        and: 'the rest client returns the result for the given parameters'
+            mockDmiRestClient.getDataJobResult(urlParams, authorization) >> Mono.just('some result')
+        when: 'the job status is queried'
+            def result = objectUnderTest.getDataJobResult(authorization, dmiServiceName,dataProducerId,  dataProducerJobId, destination)
+        then: 'the result from the rest client is returned'
+            assert result != null
+            assert  result == 'some result'
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobServiceImplSpec.groovy
new file mode 100644 (file)
index 0000000..4b536b9
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
+import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata
+import org.onap.cps.ncmp.api.datajobs.models.DataJobReadRequest
+import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest
+import org.onap.cps.ncmp.api.datajobs.models.ReadOperation
+import org.onap.cps.ncmp.api.datajobs.models.WriteOperation
+import org.slf4j.LoggerFactory
+import spock.lang.Specification
+
+class DataJobServiceImplSpec extends Specification {
+
+    def mockWriteRequestExaminer = Mock(WriteRequestExaminer)
+    def mockDmiSubJobRequestHandler = Mock(DmiSubJobRequestHandler)
+
+    def objectUnderTest = new DataJobServiceImpl(mockDmiSubJobRequestHandler, mockWriteRequestExaminer)
+
+    def myDataJobMetadata = new DataJobMetadata('', '', '')
+    def authorization = 'my authorization header'
+
+    def logger = Spy(ListAppender<ILoggingEvent>)
+
+    def setup() {
+        setupLogger()
+    }
+
+    def cleanup() {
+        ((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class)).detachAndStopAllAppenders()
+    }
+
+    def 'Read data job request.'() {
+        when: 'read data job request is processed'
+            def readOperation = new ReadOperation('', '', '', [], [], '', '', 1)
+            objectUnderTest.readDataJob(authorization, 'my-job-id', myDataJobMetadata, new DataJobReadRequest([readOperation]))
+        then: 'the data job id is correctly logged'
+            def loggingEvent = logger.list[0]
+            assert loggingEvent.level == Level.INFO
+            assert loggingEvent.formattedMessage.contains('data job id for read operation is: my-job-id')
+    }
+
+    def 'Write data-job request.'() {
+        given: 'data job metadata and write request'
+            def dataJobWriteRequest = new DataJobWriteRequest([new WriteOperation('', '', '', null)])
+        and: 'a map of producer key and dmi 3gpp write operation'
+            def dmiWriteOperationsPerProducerKey = [:]
+        when: 'write data job request is processed'
+            objectUnderTest.writeDataJob(authorization, 'my-job-id', myDataJobMetadata, dataJobWriteRequest)
+        then: 'the examiner service is called and a map is returned'
+            1 * mockWriteRequestExaminer.splitDmiWriteOperationsFromRequest('my-job-id', dataJobWriteRequest) >> dmiWriteOperationsPerProducerKey
+        and: 'the dmi request handler is called with the result from the examiner'
+            1 * mockDmiSubJobRequestHandler.sendRequestsToDmi(authorization, 'my-job-id', myDataJobMetadata, dmiWriteOperationsPerProducerKey)
+    }
+
+    def setupLogger() {
+        def setupLogger = ((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class))
+        setupLogger.setLevel(Level.DEBUG)
+        setupLogger.addAppender(logger)
+        logger.start()
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy
new file mode 100644 (file)
index 0000000..be46d88
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.datajobs
+
+import org.onap.cps.ncmp.impl.dmi.DmiProperties
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
+import reactor.core.publisher.Mono
+import spock.lang.Specification
+
+class DataJobStatusServiceImplSpec extends Specification {
+
+    def mockDmiRestClient = Mock(DmiRestClient)
+    def mockDmiProperties = Mock(DmiProperties)
+    def objectUnderTest = new DataJobStatusServiceImpl(mockDmiRestClient, mockDmiProperties)
+
+    def setup() {
+        mockDmiProperties.dmiBasePath >> 'dmi'
+    }
+
+    def 'Forward a data job status query to DMI.' () {
+        given: 'the required parameters for querying'
+            def dmiServiceName = 'some-dmi-service'
+            def dataProducerId = 'some-data-producer-id'
+            def dataProducerJobId = 'some-data-producer-job-id'
+            def authorization = 'my authorization header'
+            def urlParams = new UrlTemplateParameters('some-dmi-service/dmi/v1/cmwriteJob/dataProducer/{dataProducerId}/dataProducerJob/{dataProducerJobId}/status', ['dataProducerId':'some-data-producer-id', 'dataProducerJobId':'some-data-producer-job-id'])
+        and: 'the rest client returns a status for the given parameters'
+            mockDmiRestClient.getDataJobStatus(urlParams, authorization) >> Mono.just('some status')
+        when: 'the job status is queried'
+            def status = objectUnderTest.getDataJobStatus(authorization, dmiServiceName, dataProducerId, dataProducerJobId)
+        then: 'the status from the rest client is returned'
+            assert status == 'some status'
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandlerSpec.groovy
new file mode 100644 (file)
index 0000000..041fbd9
--- /dev/null
@@ -0,0 +1,40 @@
+package org.onap.cps.ncmp.impl.datajobs
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.data.models.OperationType
+import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata
+import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation
+import org.onap.cps.ncmp.api.datajobs.models.ProducerKey
+import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse
+import org.onap.cps.ncmp.impl.dmi.DmiProperties
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient
+import org.onap.cps.ncmp.impl.models.RequiredDmiService
+import org.onap.cps.utils.JsonObjectMapper
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import spock.lang.Specification
+
+class DmiSubJobRequestHandlerSpec extends Specification {
+
+    def mockDmiRestClient = Mock(DmiRestClient)
+    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    def mockDmiProperties = Mock(DmiProperties)
+    def objectUnderTest = new DmiSubJobRequestHandler(mockDmiRestClient, mockDmiProperties, jsonObjectMapper)
+
+    def 'Send a sub-job request to the DMI Plugin.'() {
+        given: 'a data job id, metadata and a map of producer keys and write operations to create a request'
+            def dataJobId = 'some-job-id'
+            def dataJobMetadata = new DataJobMetadata('d1', 't1', 't2')
+            def dmiWriteOperation = new DmiWriteOperation('p', 'operation', 'tag', null, 'o1', [:])
+            def dmiWriteOperationsPerProducerKey = [new ProducerKey('dmi1', 'prod1'): [dmiWriteOperation]]
+            def authorization = 'my authorization header'
+        and: 'the dmi rest client will return a response (for the correct parameters)'
+            def responseEntity = new ResponseEntity<>(new SubJobWriteResponse('my-sub-job-id', 'dmi1', 'prod1'), HttpStatus.OK)
+            def expectedJson = '{"destination":"d1","dataAcceptType":"t1","dataContentType":"t2","dataProducerId":"prod1","dataJobId":"some-job-id","data":[{"path":"p","op":"operation","moduleSetTag":"tag","value":null,"operationId":"o1","privateProperties":{}}]}'
+            mockDmiRestClient.synchronousPostOperationWithJsonData(RequiredDmiService.DATA, _, expectedJson, OperationType.CREATE, authorization) >> responseEntity
+        when: 'sending request to DMI invoked'
+            objectUnderTest.sendRequestsToDmi(authorization, dataJobId, dataJobMetadata, dmiWriteOperationsPerProducerKey)
+        then: 'the result contains the expected sub-job id'
+            assert responseEntity.body.subJobId == 'my-sub-job-id'
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy
new file mode 100644 (file)
index 0000000..84eb78b
--- /dev/null
@@ -0,0 +1,63 @@
+
+package org.onap.cps.ncmp.impl.datajobs
+
+import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest
+import org.onap.cps.ncmp.api.datajobs.models.WriteOperation
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+import org.onap.cps.spi.model.DataNode
+import spock.lang.Specification
+
+class WriteRequestExaminerSpec extends Specification {
+
+    def mockAlternateIdMatcher = Mock(AlternateIdMatcher)
+    def objectUnderTest = new WriteRequestExaminer(mockAlternateIdMatcher)
+
+    def setup() {
+        def ch1 = new DataNode(leaves: [id: 'ch1', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p1'])
+        def ch2 = new DataNode(leaves: [id: 'ch2', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p1'])
+        def ch3 = new DataNode(leaves: [id: 'ch3', 'dmi-service-name': 'dmiA', 'data-producer-identifier': 'p2'])
+        def ch4 = new DataNode(leaves: [id: 'ch4', 'dmi-service-name': 'dmiB', 'data-producer-identifier': 'p1'])
+        mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn1', '/') >> ch1
+        mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn2', '/') >> ch2
+        mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn3', '/') >> ch3
+        mockAlternateIdMatcher.getCmHandleDataNodeByLongestMatchingAlternateId('fdn4', '/') >> ch4
+    }
+
+    def 'Create a map of dmi write requests per producer key with #scenario.'() {
+        given: 'a write request with some write operations'
+            def writeOperations = writeOperationFdns.collect {
+                new WriteOperation(it, '', '', null)
+            }
+        and: 'operations are wrapped in a write request'
+            def dataJobWriteRequest = new DataJobWriteRequest(writeOperations)
+        when: 'the DMI write operations are split from the request'
+            def dmiWriteOperationsPerProducerKey = objectUnderTest.splitDmiWriteOperationsFromRequest('some id', dataJobWriteRequest)
+        then: 'we get the expected number of keys and values.'
+            def producerKeysAsStrings = dmiWriteOperationsPerProducerKey.keySet().collect {
+                it.toString()
+            }
+            assert producerKeysAsStrings.size() == expectedKeys.size()
+            assert expectedKeys.containsAll(producerKeysAsStrings)
+        where:
+            scenario                                                          | writeOperationFdns               || expectedKeys
+            'one fdn'                                                         | ['fdn1']                         || ['dmiA#p1']
+            'a duplicated target'                                             | ['fdn1','fdn1']                  || ['dmiA#p1']
+            'two different targets'                                           | ['fdn1','fdn2']                  || ['dmiA#p1']
+            'two different targets and different producer keys'               | ['fdn1','fdn3']                  || ['dmiA#p1', 'dmiA#p2']
+            'two different targets and different DMI services'                | ['fdn1','fdn4']                  || ['dmiA#p1', 'dmiB#p1']
+            'many targets with different dmi service names and producer keys' | ['fdn1', 'fdn2', 'fdn3', 'fdn4'] || ['dmiA#p1', 'dmiA#p2', 'dmiB#p1']
+    }
+
+    def 'Validate the ordering of the created sub jobs.'() {
+        given: 'a few write operations for the same producer'
+            def writeOperations = (1..3).collect {
+                new WriteOperation('fdn1', '', it.toString(), null)
+            }
+        and: 'operation is wrapped in a write request'
+            def dataJobWriteRequest = new DataJobWriteRequest(writeOperations)
+        when: 'the DMI write operations are split from the request'
+            def dmiWriteOperations = objectUnderTest.splitDmiWriteOperationsFromRequest('some id', dataJobWriteRequest).values().iterator().next()
+        then: 'we get the operation ids in the expected order.'
+            assert dmiWriteOperations.operationId == ['1', '2', '3']
+    }
+}
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations
+package org.onap.cps.ncmp.impl.dmi
 
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.spi.utils.CpsValidator
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.spockframework.spring.SpringBean
 import spock.lang.Shared
 import spock.lang.Specification
@@ -44,17 +40,13 @@ abstract class DmiOperationsBaseSpec extends Specification {
     @SpringBean
     InventoryPersistence mockInventoryPersistence = Mock()
 
-    def mockCpsValidator = Mock(CpsValidator)
-
     @SpringBean
     ObjectMapper spyObjectMapper = Spy()
 
-    @SpringBean
-    DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new NcmpConfiguration.DmiProperties(), mockCpsValidator)
-
     def yangModelCmHandle = new YangModelCmHandle()
-    def static dmiServiceName = 'some service name'
+    def static dmiServiceName = 'myServiceName'
     def static cmHandleId = 'some-cm-handle'
+    def static alternateId = 'alt-id-' + cmHandleId
     def static resourceIdentifier = 'parent/child'
 
     def mockYangModelCmHandleRetrieval(dmiProperties) {
@@ -68,7 +60,7 @@ abstract class DmiOperationsBaseSpec extends Specification {
     }
 
     def mockYangModelCmHandleCollectionRetrieval(dmiProperties) {
-        populateYangModelCmHandle(dmiProperties, "")
+        populateYangModelCmHandle(dmiProperties, '')
         mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle]
     }
 
@@ -77,6 +69,7 @@ abstract class DmiOperationsBaseSpec extends Specification {
         yangModelCmHandle.dmiServiceName = dmiServiceName
         yangModelCmHandle.dmiProperties = dmiProperties
         yangModelCmHandle.id = cmHandleId
+        yangModelCmHandle.alternateId = alternateId
         yangModelCmHandle.compositeState = new CompositeState()
         yangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY
         yangModelCmHandle.moduleSetTag = moduleSetTag
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiPropertiesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiPropertiesSpec.groovy
new file mode 100644 (file)
index 0000000..418b3bb
--- /dev/null
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.dmi
+
+
+import spock.lang.Specification
+
+class DmiPropertiesSpec extends Specification {
+
+    def objectUnderTest = new DmiProperties()
+
+    def 'Geting dmi base path.'() {
+        given: 'base path of #dmiBasePath'
+            objectUnderTest.dmiBasePath = dmiBasePath
+        expect: 'Preceding and trailing slash wil be removed'
+            assert objectUnderTest.getDmiBasePath() == 'test'
+        where: 'the following dmi base paths are used'
+            dmiBasePath << [ 'test' , '/test', 'test/', '/test/' ]
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy
new file mode 100644 (file)
index 0000000..4d47ef1
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications 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.impl.dmi
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.node.ObjectNode
+import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
+import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.utils.JsonObjectMapper
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.http.HttpStatusCode
+import org.springframework.http.ResponseEntity
+import org.springframework.web.client.HttpServerErrorException
+import org.springframework.web.reactive.function.client.WebClient
+import org.springframework.web.reactive.function.client.WebClientRequestException
+import org.springframework.web.reactive.function.client.WebClientResponseException
+import reactor.core.publisher.Mono
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.data.models.OperationType.READ
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL
+
+class DmiRestClientSpec extends Specification {
+
+    static final NO_AUTH_HEADER = null
+    static final BASIC_AUTH_HEADER = 'Basic c29tZSB1c2VyOnNvbWUgcGFzc3dvcmQ='
+    static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token'
+    static final urlTemplateParameters = new UrlTemplateParameters('/{pathParam1}/{pathParam2}', ['pathParam1': 'my', 'pathParam2': 'url'])
+
+    def mockDataServicesWebClient = Mock(WebClient)
+    def mockModelServicesWebClient = Mock(WebClient)
+    def mockHealthChecksWebClient = Mock(WebClient)
+
+    def mockRequestBody = Mock(WebClient.RequestBodyUriSpec)
+    def mockResponse = Mock(WebClient.ResponseSpec)
+
+    def mockDmiProperties = Mock(DmiProperties)
+
+    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+
+    DmiRestClient objectUnderTest = new DmiRestClient(mockDmiProperties, jsonObjectMapper, mockDataServicesWebClient, mockModelServicesWebClient, mockHealthChecksWebClient)
+
+    def setup() {
+        mockRequestBody.uri(_,_) >> mockRequestBody
+        mockRequestBody.headers(_) >> mockRequestBody
+        mockRequestBody.body(_) >> mockRequestBody
+        mockRequestBody.retrieve() >> mockResponse
+    }
+
+    def 'DMI POST Operation with JSON for DMI Data Service '() {
+        given: 'the Data web client returns a valid response entity for the expected parameters'
+            mockDataServicesWebClient.post() >> mockRequestBody
+            mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Data service', HttpStatus.I_AM_A_TEAPOT))
+        when: 'POST operation is invoked fro Data Service'
+            def response = objectUnderTest.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER)
+        then: 'the output of the method is equal to the output from the test template'
+            assert response.statusCode == HttpStatus.I_AM_A_TEAPOT
+            assert response.body == 'from Data service'
+    }
+
+    def 'DMI POST Operation with JSON for DMI Model Service '() {
+        given: 'the Model web client returns a valid response entity for the expected parameters'
+            mockModelServicesWebClient.post() >> mockRequestBody
+            mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Model service', HttpStatus.I_AM_A_TEAPOT))
+        when: 'POST operation is invoked for Model Service'
+            def response = objectUnderTest.synchronousPostOperationWithJsonData(MODEL, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER)
+        then: 'the output of the method is equal to the output from the test template'
+            assert response.statusCode == HttpStatus.I_AM_A_TEAPOT
+            assert response.body == 'from Model service'
+    }
+
+    def 'Dmi service sends client error response when #scenario'() {
+        given: 'the web client unable to return response entity but error'
+            mockDataServicesWebClient.post() >> mockRequestBody
+            mockResponse.toEntity(Object.class) >> Mono.error(exceptionType)
+        when: 'POST operation is invoked'
+            objectUnderTest.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER)
+        then: 'a http client exception is thrown'
+            def thrown = thrown(DmiClientRequestException)
+        and: 'the exception has the relevant details from the error response'
+            assert thrown.ncmpResponseStatus == expectedNcmpResponseStatusCode
+            assert thrown.httpStatusCode == httpStatusCode
+        where: 'the following errors occur'
+            scenario                  | httpStatusCode | exceptionType                                                                                    || expectedNcmpResponseStatusCode
+            'dmi service unavailable' | 503            | new WebClientRequestException(new RuntimeException('some-error'), null, null, new HttpHeaders()) || DMI_SERVICE_NOT_RESPONDING
+            'dmi request timeout'     | 408            | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null)        || DMI_SERVICE_NOT_RESPONDING
+            'dmi server error'        | 500            | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null)        || UNABLE_TO_READ_RESOURCE_DATA
+            'dmi service unavailable' | 503            | new HttpServerErrorException(HttpStatusCode.valueOf(503))                                        || DMI_SERVICE_NOT_RESPONDING
+            'unknown error'           | 500            | new Throwable('message')                                                                         || UNKNOWN_ERROR
+    }
+
+    def 'Dmi trust level is determined by spring boot health status'() {
+        given: 'a health check response'
+            def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json')
+            def jsonNode = jsonObjectMapper.convertJsonString(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
+            ((ObjectNode) jsonNode).put('status', 'my status')
+            mockHealthChecksWebClient.get() >> mockRequestBody
+            mockResponse.onStatus(_,_)>> mockResponse
+            mockResponse.bodyToMono(JsonNode.class) >> Mono.just(jsonNode)
+        when: 'get trust level of the dmi plugin'
+            def urlTemplateParameters = new UrlTemplateParameters('some url', [:])
+            def result = objectUnderTest.getDmiHealthStatus(urlTemplateParameters).block()
+        then: 'the status value from the json is returned'
+            assert result == 'my status'
+    }
+
+    def 'Failing to get dmi plugin health status #scenario'() {
+        given: 'web client instance with #scenario'
+            mockHealthChecksWebClient.get() >> mockRequestBody
+            mockResponse.onStatus(_, _) >> mockResponse
+            mockResponse.bodyToMono(_) >> Mono.error(exceptionType)
+        when: 'attempt to get health status of the dmi plugin'
+            def urlTemplateParameters = new UrlTemplateParameters('some url', [:])
+            def result = objectUnderTest.getDmiHealthStatus(urlTemplateParameters).block()
+        then: 'result will be empty'
+            assert result == ''
+        where: 'the following responses are used'
+            scenario                  | exceptionType
+            'dmi request timeout'     | new WebClientResponseException('some-message', 408, 'some-text', null, null, null)
+            'dmi service unavailable' | new HttpServerErrorException(HttpStatus.SERVICE_UNAVAILABLE)
+    }
+
+    def 'DMI auth header #scenario'() {
+        when: 'Specific dmi properties are provided'
+            mockDmiProperties.dmiBasicAuthEnabled >> authEnabled
+            mockDmiProperties.authUsername >> 'some user'
+            mockDmiProperties.authPassword >> 'some password'
+        then: 'http headers to conditionally have Authorization header'
+            def httpHeaders = new HttpHeaders()
+            objectUnderTest.configureHttpHeaders(httpHeaders, ncmpAuthHeader)
+            def outputAuthHeader = (httpHeaders.Authorization == null ? null : httpHeaders.Authorization[0])
+            assert outputAuthHeader == expectedAuthHeader
+        where: 'the following configurations are used'
+            scenario                                          | authEnabled | ncmpAuthHeader     || expectedAuthHeader
+            'DMI basic auth enabled, no NCMP bearer token'    | true        | NO_AUTH_HEADER     || BASIC_AUTH_HEADER
+            'DMI basic auth enabled, with NCMP bearer token'  | true        | BEARER_AUTH_HEADER || BASIC_AUTH_HEADER
+            'DMI basic auth disabled, no NCMP bearer token'   | false       | NO_AUTH_HEADER     || NO_AUTH_HEADER
+            'DMI basic auth disabled, with NCMP bearer token' | false       | BEARER_AUTH_HEADER || BEARER_AUTH_HEADER
+            'DMI basic auth disabled, with NCMP basic auth'   | false       | BASIC_AUTH_HEADER  || NO_AUTH_HEADER
+    }
+
+    def 'DMI GET Operation for DMI Data Service '() {
+        given: 'the Data web client returns a valid response entity for the expected parameters'
+            mockDataServicesWebClient.get() >> mockRequestBody
+            def jsonNode = jsonObjectMapper.convertJsonString('{"status":"some status"}', JsonNode.class)
+            ((ObjectNode) jsonNode).put('status', 'some status')
+            mockResponse.bodyToMono(JsonNode.class) >> Mono.just(jsonNode)
+        when: 'GET operation is invoked for Data Service'
+            def response = objectUnderTest.getDataJobStatus(urlTemplateParameters, NO_AUTH_HEADER).block()
+        then: 'the response equals to the expected value'
+            assert response == 'some status'
+    }
+
+    def 'Get data job result from DMI.'() {
+        given: 'the Data web client returns a valid response entity for the expected parameters'
+            mockDataServicesWebClient.get() >> mockRequestBody
+            def result = 'some result'
+            mockResponse.bodyToMono(String.class) >> Mono.just(result)
+        when: 'GET operation is invoked for Data Service'
+            def response = objectUnderTest.getDataJobResult(urlTemplateParameters, NO_AUTH_HEADER).block()
+        then: 'the response has some value'
+            assert response != null
+            assert  result == 'some result'
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfigurationSpec.groovy
new file mode 100644 (file)
index 0000000..cb209be
--- /dev/null
@@ -0,0 +1,70 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.dmi
+
+
+import org.onap.cps.ncmp.config.DmiHttpClientConfig
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.web.reactive.function.client.WebClient
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [DmiHttpClientConfig])
+@EnableConfigurationProperties
+class DmiWebClientsConfigurationSpec extends Specification {
+
+    def webClientBuilder = Mock(WebClient.Builder) {
+        defaultHeaders(_) >> it
+        clientConnector(_) >> it
+        codecs(_) >> it
+        build() >> Mock(WebClient)
+    }
+
+    def dmiHttpClientConfiguration = Spy(DmiHttpClientConfig.class)
+
+    def objectUnderTest = new DmiWebClientsConfiguration(dmiHttpClientConfiguration)
+
+    def 'Web client for data services.'() {
+        when: 'creating a web client for dmi data services'
+            def result = objectUnderTest.dataServicesWebClient(webClientBuilder)
+        then: 'a web client is created successfully'
+            assert result != null
+            assert result instanceof WebClient
+    }
+
+    def 'Web client model services.'() {
+        when: 'creating a web client for dmi model services'
+            def result = objectUnderTest.modelServicesWebClient(webClientBuilder)
+        then: 'a web client is created successfully'
+            assert result != null
+            assert result instanceof WebClient
+    }
+
+    def 'Web client health check services.'() {
+        when: 'creating a web client for dmi health check services'
+            def result = objectUnderTest.healthChecksWebClient(webClientBuilder)
+        then: 'a web client is created successfully'
+            assert result != null
+            assert result instanceof WebClient
+    }
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils
+package org.onap.cps.ncmp.impl.inventory
 
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.model.DataNode
-import org.onap.cps.spi.model.DataNodeBuilder
 import spock.lang.Specification
 
 class AlternateIdCheckerSpec extends Specification {
 
     def mockInventoryPersistenceService = Mock(InventoryPersistence)
-    def someDataNode = new DataNodeBuilder().build()
-    def dataNodeFoundException = new DataNodeNotFoundException('', '')
 
     def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService)
 
-    def 'Check new cm handle with new alternate id.'() {
-        given: 'inventory persistence can not find cm handle id'
-            mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException}
-        and: 'inventory persistence can not find alternate id'
-            mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> {throw dataNodeFoundException}
-        expect: 'mapping can be added'
-             assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id')
-    }
-
-    def 'Check new cm handle with used alternate id.'() {
-        given: 'inventory persistence can not find cm handle id'
-            mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException}
-        and: 'inventory persistence can find alternate id'
-            mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> { someDataNode }
-        expect: 'mapping can not be added'
-            assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id') == false
-    }
-
-    def 'Check for existing cm handle with #currentAlternateId.'() {
-        given: 'a cm handle with the #currentAlternateId'
-            def yangModelCmHandle = new YangModelCmHandle(alternateId: currentAlternateId)
-        and: 'inventory service finds the cm handle'
-            mockInventoryPersistenceService.getYangModelCmHandle('my cm handle') >> yangModelCmHandle
-        expect: 'add mapping returns expected result'
-            assert canAdd == objectUnderTest.canApplyAlternateId('my cm handle', 'same alternate id')
-        where: 'following alternate ids is used'
-            currentAlternateId   || canAdd
-            'same alternate id'  || true
-            'other alternate id' || false
-    }
-
     def 'Check a batch of created cm handles with #scenario.'() {
-        given: 'a batch of 2 new cm handles alternate id ids #alt1 and #alt2'
+        given: 'a batch of 2 new cm handles with alternate ids #alt1 and #alt2'
             def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: alt1),
                          new NcmpServiceCmHandle(cmHandleId: 'ch-2', alternateId: alt2)]
         and: 'the database already contains cm handle(s) with these alternate ids: #altAlreadyInDb'
-            mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId(_) >>
-                {  args -> altAlreadyInDb.contains(args[0]) ? new DataNode() : throwDataNodeNotFoundException() }
+            mockInventoryPersistenceService.getCmHandleDataNodesByAlternateIds(_ as Collection<String>) >>
+                { args -> args[0].stream().filter(altId -> altAlreadyInDb.contains(altId)).map(altId -> new DataNode(leaves: ["alternate-id": altId])).toList() }
         when: 'the batch of new cm handles is checked'
             def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.CREATE)
         then: 'the result contains ids of the rejected cm handles'
             assert result == expectedRejectedCmHandleIds
         where: 'the following alternate ids are used'
-            scenario                          | alt1   | alt2   | altAlreadyInDb  || expectedRejectedCmHandleIds
-            'blank alternate ids'             | ''     | ''     | ['dont matter'] || []
-            'null alternate ids'              | null   | null   | ['dont matter'] || []
-            'new alternate ids'               | 'fdn1' | 'fdn2' | ['other fdn']   || []
-            'one already used alternate id'   | 'fdn1' | 'fdn2' | ['fdn1']        || ['ch-1']
-            'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter'] || ['ch-2']
+            scenario                          | alt1   | alt2   | altAlreadyInDb   || expectedRejectedCmHandleIds
+            'blank alternate ids'             | ''     | ''     | ['dont matter']  || []
+            'null alternate ids'              | null   | null   | ['dont matter']  || []
+            'new alternate ids'               | 'fdn1' | 'fdn2' | ['other fdn']    || []
+            'one already used alternate id'   | 'fdn1' | 'fdn2' | ['fdn1']         || ['ch-1']
+            'two already used alternate ids'  | 'fdn1' | 'fdn2' | ['fdn1', 'fdn2'] || ['ch-1', 'ch-2']
+            'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter']  || ['ch-2']
     }
 
     def 'Check a batch of updates to existing cm handles with #scenario.'() {
-        given: 'a batch of 1 existing cm handle update alternate id to #proposedAlt'
+        given: 'a batch of 1 existing cm handle to update alternate id to #proposedAlt'
             def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: proposedAlt)]
         and: 'the database already contains a cm handle with alternate id: #altAlreadyInDb'
-            mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId(_) >>
-                    {  args -> altAlreadyInDb.equals(args[0]) ? new DataNode() : throwDataNodeNotFoundException() }
+            mockInventoryPersistenceService.getCmHandleDataNodesByAlternateIds(_ as Collection<String>) >>
+                { args -> args[0].stream().filter(altId -> altAlreadyInDb == altId).map(altId -> new DataNode(leaves: ["alternate-id": altId])).toList() }
             mockInventoryPersistenceService.getYangModelCmHandle(_) >> new YangModelCmHandle(alternateId: altAlreadyInDb)
         when: 'the batch of cm handle updates is checked'
             def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE)
@@ -105,9 +71,20 @@ class AlternateIdCheckerSpec extends Specification {
             'used different alternate id' | 'otherFdn'  | 'fdn1'         || ['ch-1']
     }
 
-    def throwDataNodeNotFoundException() {
-        // cannot 'return' an exception in conditional stub behavior, so hence a method call that will always throw this exception
-        throw dataNodeFoundException
+    def 'Check update of non-existing cm handle.'() {
+        given: 'a batch of 1 non-existing cm handle to update alternate id'
+            def batch = [new NcmpServiceCmHandle(cmHandleId: 'non-existing', alternateId: 'altId')]
+        and: 'the database does not contain any cm handles'
+            mockInventoryPersistenceService.getCmHandleDataNodesByAlternateIds(_) >> []
+            mockInventoryPersistenceService.getYangModelCmHandle(_) >> { throwDataNodeNotFoundException() }
+        when: 'the batch of cm handle updates is checked'
+            def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE)
+        then: 'the result has no rejected cm handles'
+            assert result.empty
+    }
+
+    static throwDataNodeNotFoundException() {
+        throw new DataNodeNotFoundException('', '')
     }
 
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils
+package org.onap.cps.ncmp.impl.inventory
 
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.ConditionProperties
 import spock.lang.Specification
 
-class RestQueryParametersValidatorSpec extends Specification {
-
+class CmHandleQueryParametersValidatorSpec extends Specification {
 
     def 'CM Handle Query validation: empty query.'() {
         given: 'a cm handle query'
             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
         when: 'validator is invoked'
-            RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, [])
+            CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, [])
         then: 'data validation exception is not thrown'
             noExceptionThrown()
     }
@@ -45,7 +44,7 @@ class RestQueryParametersValidatorSpec extends Specification {
             condition.conditionParameters = [['key':'value']]
         cmHandleQueryParameters.cmHandleQueryParameters = [condition]
         when: 'validator is invoked'
-            RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName'])
+            CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName'])
         then: 'data validation exception is not thrown'
             noExceptionThrown()
     }
@@ -58,17 +57,17 @@ class RestQueryParametersValidatorSpec extends Specification {
             condition.conditionParameters = conditionParameters
             cmHandleQueryParameters.cmHandleQueryParameters = [condition]
         when: 'validator is invoked'
-            RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName'])
+            CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName'])
         then: 'a data validation exception is thrown'
             def thrown = thrown(DataValidationException)
         and: 'the exception details contain the correct significant term '
-            thrown.details.contains(expectedWordInDetails)
+            assert thrown.details.contains(expectedWordInDetails)
         where:
             scenario                 | conditionName        | conditionParameters                || expectedWordInDetails
             'unknown condition name' | 'unknownCondition'   | [['key': 'value']]                 || 'conditionName'
             'no condition name'      | ''                   | [['key': 'value']]                 || 'conditionName'
+            'empty conditions'       | 'validConditionName' | []                                 || 'conditionsParameters'
             'empty properties'       | 'validConditionName' | [[:]]                              || 'conditionsParameter'
-            'empty conditions'       | 'validConditionName' | [[:]]                              || 'conditionsParameter'
             'too many properties'    | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter'
             'empty key'              | 'validConditionName' | [['': 'wrong']]                    || 'conditionsParameter'
     }
@@ -77,14 +76,14 @@ class RestQueryParametersValidatorSpec extends Specification {
         given: 'a condition property'
             def conditionProperty = [moduleName: 'value']
         when: 'validator is invoked'
-            RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty)
+            CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty)
         then: 'data validation exception is not thrown'
             noExceptionThrown()
     }
 
     def 'CM Handle Query validation: validate module name condition properties - #scenario.'() {
         when: 'validator is invoked'
-            RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty)
+            CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty)
         then: 'a data validation exception is thrown'
             thrown(DataValidationException)
         where:
@@ -95,7 +94,7 @@ class RestQueryParametersValidatorSpec extends Specification {
 
     def 'Validate CmHandle where an exception is thrown due to #scenario.'() {
         when: 'the validator is called on a cps path condition property'
-            RestQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty)
+            CmHandleQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty)
         then: 'a data validation exception is thrown'
             def e = thrown(DataValidationException)
         and: 'exception message matches the expected message'
@@ -109,12 +108,12 @@ class RestQueryParametersValidatorSpec extends Specification {
 
     def 'No conditions.'() {
         expect: 'no conditions always returns true'
-            RestQueryParametersValidator.validateCpsPathConditionProperties([:]) == true
+            CmHandleQueryParametersValidator.validateCpsPathConditionProperties([:]) == true
     }
 
     def 'Validate CmHandle where #scenario.'() {
         when: 'the validator is called on a cps path condition property'
-            def result = RestQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath])
+            def result = CmHandleQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath])
         then: 'the expected boolean value is returned'
             result == expectedBoolean
         where:
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-2024 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory
+package org.onap.cps.ncmp.impl.inventory
 
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
-import org.onap.cps.spi.utils.CpsValidator
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsQueryService
+import org.onap.cps.impl.utils.CpsValidator
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
 import org.onap.cps.spi.model.DataNode
 import spock.lang.Shared
 import spock.lang.Specification
 
-class CmHandleQueriesImplSpec extends Specification {
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
 
-    def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
+class CmHandleQueryServiceImplSpec extends Specification {
+
+    def mockCpsQueryService = Mock(CpsQueryService)
+    def mockCpsDataService = Mock(CpsDataService)
 
     def trustLevelPerDmiPlugin = [:]
 
@@ -43,7 +47,7 @@ class CmHandleQueriesImplSpec extends Specification {
 
     def mockCpsValidator = Mock(CpsValidator)
 
-    def objectUnderTest = new CmHandleQueriesImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator)
+    def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataService, mockCpsQueryService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator)
 
     @Shared
     def static sampleDataNodes = [new DataNode()]
@@ -102,7 +106,7 @@ class CmHandleQueriesImplSpec extends Specification {
 
     def 'Query CmHandles by a private field\'s value.'() {
         given: 'a data node exists with a certain additional-property'
-            mockCpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5]
+            mockCpsQueryService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5]
         when: 'a query on CmHandle private properties is executed using a map'
             def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com'])
         then: 'one cm handle is returned'
@@ -113,7 +117,7 @@ class CmHandleQueriesImplSpec extends Specification {
         given: 'a cm handle state to query'
             def cmHandleState = CmHandleState.ADVISED
         and: 'the persistence service returns a list of data nodes'
-            mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> sampleDataNodes
         when: 'cm handles are fetched by state'
             def result = objectUnderTest.queryCmHandlesByState(cmHandleState)
@@ -125,7 +129,7 @@ class CmHandleQueriesImplSpec extends Specification {
         given: 'a cm handle state to compare'
             def cmHandleState = state
         and: 'the persistence service returns a list of data nodes'
-            mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                     NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state',
                     OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
         when: 'cm handles are compared by state'
@@ -142,7 +146,7 @@ class CmHandleQueriesImplSpec extends Specification {
         given: 'a cm handle state to query'
             def cmHandleState = CmHandleState.READY
         and: 'cps data service returns a list of data nodes'
-            mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                     NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state',
                     OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
         when: 'cm handles are fetched by state and id'
@@ -155,7 +159,7 @@ class CmHandleQueriesImplSpec extends Specification {
         given: 'a cm handle state to query'
             def cmHandleState = CmHandleState.READY
         and: 'cps data service returns a list of data nodes'
-            mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
         when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state'
             def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED)
@@ -165,10 +169,10 @@ class CmHandleQueriesImplSpec extends Specification {
 
     def 'Retrieve cm handle by cps path '() {
         given: 'a cm handle state to query based on the cps path'
-            def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED'])
-            def cpsPath = '//cps-path'
-        and: 'cps data service returns a valid data node'
-            mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            def cmHandleDataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']", leaves: ['id': 'ch-1'])
+            def cpsPath = "//state[@cm-handle-state='LOCKED']"
+        and: 'cps data service returns a valid data node for cm handle ancestor'
+            mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS)
                 >> Arrays.asList(cmHandleDataNode)
         when: 'get cm handles by cps path is invoked'
@@ -177,6 +181,20 @@ class CmHandleQueriesImplSpec extends Specification {
             assert result.contains(cmHandleDataNode)
     }
 
+    def 'Retrieve cm handle by cps path querying cm handle directly'() {
+        given: 'a cm handle to query based on the cps path'
+            def cmHandleDataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-2']", leaves: ['id': 'ch-2'])
+            def cpsPath = "//cm-handles[@alternate-id='1']"
+        and: 'cps data service returns a valid data node'
+            mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                    cpsPath, INCLUDE_ALL_DESCENDANTS)
+                    >> Arrays.asList(cmHandleDataNode)
+        when: 'get cm handles by cps path is invoked'
+            def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS)
+        then: 'the returned result is a list of data nodes returned by cps data service'
+            assert result.contains(cmHandleDataNode)
+    }
+
     def 'Get all cm handles by dmi plugin identifier'() {
         given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.'
             mockResponses()
@@ -189,15 +207,15 @@ class CmHandleQueriesImplSpec extends Specification {
     }
 
     void mockResponses() {
-        mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4]
-        mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> []
-        mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4]
-        mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> []
-        mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3]
-        mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4]
-        mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2]
-        mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4]
-        mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4]
+        mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4]
+        mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> []
+        mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4]
+        mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> []
+        mockCpsQueryService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3]
+        mockCpsQueryService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4]
+        mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2]
+        mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4]
+        mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4]
     }
 
     def static createDataNode(dataNodeId) {
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * Copyright (C) 2022-2024 Nordix Foundation
  * Modifications Copyright (C) 2022 Bell Canada
- * Modifications Copyright (C) 2023 TechMahindra Ltd.
+ * Modifications Copyright (C) 2024 TechMahindra Ltd.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl
+package org.onap.cps.ncmp.impl.inventory
 
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
@@ -28,13 +28,12 @@ import ch.qos.logback.classic.spi.ILoggingEvent
 import ch.qos.logback.core.read.ListAppender
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.api.CpsDataService
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
 import org.slf4j.LoggerFactory
 import spock.lang.Specification
@@ -42,22 +41,22 @@ import spock.lang.Specification
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
+import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
 
-class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
+class CmHandleRegistrationServicePropertyHandlerSpec extends Specification {
 
     def mockInventoryPersistence = Mock(InventoryPersistence)
     def mockCpsDataService = Mock(CpsDataService)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
     def mockAlternateIdChecker = Mock(AlternateIdChecker)
 
-    def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker)
+    def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker)
     def logger = Spy(ListAppender<ILoggingEvent>)
 
     void setup() {
-        def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class))
+        def setupLogger = ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class))
         setupLogger.addAppender(logger)
         setupLogger.setLevel(Level.DEBUG)
         logger.start()
@@ -66,7 +65,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
     }
 
     void cleanup() {
-        ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).detachAndStopAllAppenders()
+        ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)).detachAndStopAllAppenders()
     }
 
     def static cmHandleId = 'myHandle1'
@@ -209,7 +208,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
         when: 'cm handle properties is updated'
             def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
         then: 'the update is delegated to cps data service with correct parameters'
-            1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >>
+            1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >>
                     { args ->
                         assert args[3].contains('alt-1')
                     }
@@ -241,12 +240,12 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
         given: 'an existing cm handle with no data producer identifier'
             DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': oldDataProducerIdentifier])
         and: 'an update request with a new data producer identifier'
-            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'someDataProducerIdentifier')
+            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'New Data Producer Identifier')
         when: 'data producer identifier updated'
             objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle)
         then: 'the update node leaves method is invoked once'
-            1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> { args ->
-                assert args[3].contains('someDataProducerIdentifier')
+            1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args ->
+                assert args[3].contains('New Data Producer Identifier')
             }
         and: 'correct information is logged'
             def lastLoggingEvent = logger.list[0]
@@ -258,6 +257,17 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
             'blank to something' | ''
     }
 
+    def 'Update CM Handle data producer identifier with same value'() {
+        given: 'an existing cm handle with no data producer identifier'
+            DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': 'same id'])
+        and: 'an update request with a new data producer identifier'
+            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'same id')
+        when: 'data producer identifier updated'
+            objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle)
+        then: 'the update node leaves method is not invoked'
+            0 * mockCpsDataService.updateNodeLeaves(*_)
+    }
+
     def 'Update CM Handle data producer identifier from some data producer identifier to another data producer identifier'() {
         given: 'an existing cm handle with a data producer identifier'
             DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId', 'data-producer-identifier': 'someDataProducerIdentifier'])
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl
+package org.onap.cps.ncmp.impl.inventory
 
-import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
-
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager
-import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
-import org.onap.cps.ncmp.api.models.UpgradedCmHandles
-import com.fasterxml.jackson.databind.ObjectMapper
 import com.hazelcast.map.IMap
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
-import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler
-import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
-import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.exceptions.DmiRequestException
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
+import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler
+import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
-import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Specification
 
-class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
+
+class CmHandleRegistrationServiceSpec extends Specification {
 
     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')
     def mockCpsModuleService = Mock(CpsModuleService)
-    def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
-    def mockDmiDataOperations = Mock(DmiDataOperations)
-    def mockNetworkCmProxyDataServicePropertyHandler = Mock(NetworkCmProxyDataServicePropertyHandler)
+    def mockNetworkCmProxyDataServicePropertyHandler = Mock(CmHandleRegistrationServicePropertyHandler)
     def mockInventoryPersistence = Mock(InventoryPersistence)
-    def mockCmHandleQueries = Mock(CmHandleQueries)
-    def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandleQueryService)
+    def mockCmHandleQueries = Mock(CmHandleQueryService)
     def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
     def mockCpsDataService = Mock(CpsDataService)
     def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>)
-    def trustLevelPerDmiPlugin = [:]
     def mockTrustLevelManager = Mock(TrustLevelManager)
     def mockAlternateIdChecker = Mock(AlternateIdChecker)
 
-    def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations,
-        mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCmHandleQueries,
-        stubbedNetworkCmProxyCmHandlerQueryService, mockLcmEventsCmHandleStateHandler, mockCpsDataService,
-        mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager, mockAlternateIdChecker))
+    def objectUnderTest = Spy(new CmHandleRegistrationService(
+        mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler,
+        mockModuleSyncStartedOnCmHandles, mockTrustLevelManager, mockAlternateIdChecker))
 
     def setup() {
         // always accept all cm handles
@@ -152,9 +142,6 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'create cm handles registration and sync modules is called with the correct plugin information'
             1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _)
-        and: 'dmi is added to the dmi trustLevel map'
-            assert trustLevelPerDmiPlugin.size() == 1
-            assert trustLevelPerDmiPlugin.containsKey(expectedDmiPluginRegisteredName)
         where:
             scenario                          | dmiPlugin  | dmiModelPlugin | dmiDataPlugin || expectedDmiPluginRegisteredName
             'combined DMI plugin'             | 'service1' | ''             | ''            || 'service1'
@@ -221,7 +208,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         when: 'registration is updated'
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'trustLevel is set for the created cm-handle'
-            1 * mockTrustLevelManager.handleInitialRegistrationOfTrustLevels(expectedMapping)
+            1 * mockTrustLevelManager.registerCmHandles(expectedMapping)
         where:
             scenario                 | registrationTrustLevel || expectedMapping
             'with trusted cm handle' | TrustLevel.COMPLETE    || [ 'ch-1' : TrustLevel.COMPLETE ]
@@ -418,4 +405,46 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         'an unexpected exception'    | 'cmhandle'             | new RuntimeException('Failed')            || UNKNOWN_ERROR        | 'Failed'
     }
 
+    def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is  #scenario'() {
+        given: 'an existing cm handle composite state'
+            def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
+                dataStores: CompositeState.DataStores.builder()
+                    .operationalDataStore(CompositeState.Operational.builder()
+                        .dataStoreSyncState(initialDataSyncState)
+                        .build()).build())
+        and: 'get cm handle state returns the composite state for the given cm handle id'
+            mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
+        when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
+            objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
+        then: 'the data sync enabled flag is set to #dataSyncEnabled'
+            compositeState.dataSyncEnabled == dataSyncEnabledFlag
+        and: 'the data store sync state is set to #expectedDataStoreSyncState'
+            compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
+        and: 'the cps data service to delete data nodes is invoked the expected number of times'
+            deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _)
+        and: 'the inventory persistence service to update node leaves is called with the correct values'
+            saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
+        where: 'the following data sync enabled flag is used'
+            scenario                                              | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState               || expectedDataStoreSyncState         | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
+            'enabled'                                             | true                | false                      | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1
+            'disabled'                                            | false               | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.NONE_REQUESTED  | 0                                        | 1
+            'disabled where sync-state is currently SYNCHRONIZED' | false               | true                       | DataStoreSyncState.SYNCHRONIZED    || DataStoreSyncState.NONE_REQUESTED  | 1                                        | 1
+            'is set to existing flag state'                       | true                | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 0
+    }
+
+    def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
+        given: 'a cm handle composite state'
+            def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
+        and: 'get cm handle state returns the composite state for the given cm handle id'
+            mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
+        when: 'set data sync enabled is called with the data sync enabled flag set to true'
+            objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
+        then: 'the expected exception is thrown'
+            thrown(CpsException)
+        and: 'the inventory persistence service to update node leaves is not invoked'
+            0 * mockInventoryPersistence.saveCmHandleState(_, _)
+    }
+
+
+
 }
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+package org.onap.cps.ncmp.impl.inventory
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.impl.utils.CpsValidator
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.spi.CascadeDeleteAllowed
 import org.onap.cps.spi.FetchDescendantsOption
-import org.onap.cps.ncmp.api.impl.exception.NoAlternateIdParentFoundException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
-import org.onap.cps.spi.utils.CpsValidator
+import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Shared
 import spock.lang.Specification
+
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
 
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NO_TIMESTAMP
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+
 class InventoryPersistenceImplSpec extends Specification {
 
     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
@@ -62,7 +65,7 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def mockCpsValidator = Mock(CpsValidator)
 
-    def mockCmHandleQueries = Mock(CmHandleQueries)
+    def mockCmHandleQueries = Mock(CmHandleQueryService)
 
     def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
             mockCpsValidator, mockCpsAnchorService, mockCmHandleQueries)
@@ -164,7 +167,7 @@ class InventoryPersistenceImplSpec extends Specification {
         when: 'update cm handle state is invoked with the #scenario state'
             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
         then: 'update node leaves is invoked with the correct params'
-            1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime)
+            1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime, ContentType.JSON)
         where: 'the following states are used'
             scenario    | cmHandleState          || expectedJsonData
             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
@@ -180,7 +183,7 @@ class InventoryPersistenceImplSpec extends Specification {
             def cmHandleStateMap = ['Some-Cm-Handle1' : compositeState1, 'Some-Cm-Handle2' : compositeState2]
             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
         then: 'update node leaves is invoked with the correct params'
-            1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime)
+            1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime, ContentType.JSON)
         where: 'the following states are used'
             scenario    | cmHandleState          || cmHandlesJsonDataMap
             'READY'     | CmHandleState.READY    || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
@@ -229,7 +232,7 @@ class InventoryPersistenceImplSpec extends Specification {
             objectUnderTest.saveCmHandle(yangModelCmHandle)
         then: 'the data service method to save list elements is called once'
             1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
-                    _,null) >> {
+                    _,null, ContentType.JSON) >> {
                 args -> {
                     assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}')
                 }
@@ -244,7 +247,7 @@ class InventoryPersistenceImplSpec extends Specification {
             objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2])
         then: 'CPS Data Service persists both cm handles as a batch'
             1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                    NCMP_DMI_REGISTRY_PARENT, _,null) >> {
+                    NCMP_DMI_REGISTRY_PARENT, _,null, ContentType.JSON) >> {
                 args -> {
                     def jsonData = (args[3] as String)
                     jsonData.contains('cmhandle1')
@@ -294,7 +297,7 @@ class InventoryPersistenceImplSpec extends Specification {
             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS)
     }
 
-    def 'Get cm handle data node'() {
+    def 'Get cm handle data node by alternate id'() {
         given: 'expected xPath to get cmHandle data node'
             def expectedXPath = '/dmi-registry/cm-handles[@alternate-id=\'alternate id\']'
         and: 'query service is invoked with expected xpath'
@@ -303,41 +306,6 @@ class InventoryPersistenceImplSpec extends Specification {
             assert objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') == new DataNode()
     }
 
-    def 'Find cm handle parent data node using alternate ids'() {
-        given: 'cm handle in the registry with alternateId /a/b'
-            def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='/a/b']"
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()]
-        and: 'no other cm handle'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> []
-        expect: 'querying for alternate id a matching result found'
-            assert objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/') != null
-        where: 'the following parameters are used'
-            scenario                              | alternateId
-            'exact match'                         | '/a/b'
-            'exact match with trailing separator' | '/a/b/'
-            'child match'                         | '/a/b/c'
-    }
-
-    def 'Find cm handle parent data node using alternate ids mismatches'() {
-        given: 'cm handle in the registry with alternateId'
-            def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='${cpsPath}]"
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()]
-        and: 'no other cm handle'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> []
-        when: 'attempt to find alternateId'
-            objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/')
-        then: 'no alternate id found exception thrown'
-            def thrown = thrown(NoAlternateIdParentFoundException)
-        and: 'the exception has the relevant details from the error response'
-            assert thrown.message == 'No matching (parent) cm handle found using alternate ids'
-            assert thrown.details == 'cannot find a datanode with alternate id ' + alternateId
-        where: 'the following parameters are used'
-            scenario                              | alternateId | cpsPath
-            'no match for parent only'            | '/a'        | '/a/b'
-            'no match at all'                     | '/x/y/z'    | '/a/b'
-            'no match with trailing separator'    | '/c/d/'     | '/c/d'
-    }
-
     def 'Attempt to get non existing cm handle data node by alternate id'() {
         given: 'query service is invoked and returns empty collection of data nodes'
             mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> []
@@ -348,6 +316,22 @@ class InventoryPersistenceImplSpec extends Specification {
             assert thrownException.getMessage().contains('DataNode not found')
     }
 
+    def 'Get multiple cm handle data nodes by alternate ids'() {
+        given: 'expected xPath to get cmHandle data node'
+            def expectedXPath = "/dmi-registry/cm-handles[@alternate-id='A' or @alternate-id='B']"
+        when: 'getting the cm handle data node'
+            objectUnderTest.getCmHandleDataNodesByAlternateIds(['A', 'B'])
+        then: 'query service is invoked with expected xpath'
+            1 * mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS)
+    }
+
+    def 'Get multiple cm handle data nodes by alternate ids, passing empty collection'() {
+        when: 'getting the cm handle data node for no alternate ids'
+            objectUnderTest.getCmHandleDataNodesByAlternateIds([])
+        then: 'query service is not invoked'
+            0 * mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, _)
+    }
+
     def 'Get CM handles that has given module names'() {
         when: 'the method to get cm handles is called'
             objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name'])
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy
new file mode 100644 (file)
index 0000000..9e07de4
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2021 Pantheon.tech
+ *  Modifications Copyright (C) 2021-2022 Bell Canada
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  ================================================================================
+ *  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.impl.inventory
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+import org.onap.cps.spi.model.ConditionProperties
+import org.onap.cps.utils.JsonObjectMapper
+import spock.lang.Specification
+
+class NetworkCmProxyInventoryFacadeSpec extends Specification {
+
+    def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService)
+    def mockCmHandleQueryService = Mock(CmHandleQueryService)
+    def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService)
+    def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
+    def mockInventoryPersistence = Mock(InventoryPersistence)
+    def mockTrustLevelManager = Mock(TrustLevelManager)
+    def mockAlternateIdMatcher = Mock(AlternateIdMatcher)
+    def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper, mockTrustLevelManager, mockAlternateIdMatcher)
+
+    def 'Update DMI Registration'() {
+        given: 'an (updated) dmi plugin registration'
+            def dmiPluginRegistration = Mock(DmiPluginRegistration)
+        when: 'the registration is submitted '
+           objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'the call is delegated to the cm handle registration service'
+            1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+    }
+
+    def 'Execute cm handle id search for inventory'() {
+        given: 'a ConditionApiProperties object'
+            def conditionProperties = new ConditionProperties()
+            conditionProperties.conditionName = 'hasAllProperties'
+            conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
+            def cmHandleQueryServiceParameters = new CmHandleQueryServiceParameters()
+            cmHandleQueryServiceParameters.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
+        and: 'the system returns an set of cmHandle ids'
+            mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
+        when: 'executing the search'
+            def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters)
+        then: 'the result returns the correct 2 elements'
+            assert result.size() == 2
+            assert result.contains('cmHandle1')
+            assert result.contains('cmHandle2')
+    }
+
+    def 'Get all cm handle IDs by DMI plugin identifier.' () {
+        given: 'cm handle queries service returns cm handles'
+            1 * mockCmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
+        when: 'cm handle Ids are requested with dmi plugin identifier'
+            def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
+        then: 'the result size is correct'
+            assert result.size() == 2
+        and: 'the result returns the correct details'
+            assert result.containsAll('cm-handle-1','cm-handle-2')
+    }
+
+    def 'Getting Yang Resources for a given #scenario'() {
+        when: 'yang resources is called'
+            objectUnderTest.getYangResourcesModuleReferences(cmHandleRef)
+        then: 'alternate id matcher is called'
+            mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
+        and: 'CPS module services is invoked for the correct cm handle'
+            1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
+        where: 'following cm handle reference is used'
+            scenario                              | cmHandleRef
+            'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
+            'Cm Handle Reference as alternate-id' | 'some-alternate-id'
+    }
+
+    def 'Get a cm handle details using #scenario'() {
+        given: 'the system returns a yang modelled cm handle'
+            def dmiServiceName = 'some service name'
+            def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
+                lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details('lock details').build(),
+                lastUpdateTime: 'some-timestamp',
+                dataSyncEnabled: false,
+                dataStores: dataStores())
+            def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
+            def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
+            def moduleSetTag = 'some-module-set-tag'
+            def alternateId = 'some-alternate-id'
+            def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, dmiProperties: dmiProperties,
+                 publicProperties: publicProperties, compositeState: compositeState, moduleSetTag: moduleSetTag, alternateId: alternateId)
+            mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
+            1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
+        and: 'a trust level for the cm handle in the cache'
+            mockTrustLevelManager.getEffectiveTrustLevel(*_) >> TrustLevel.COMPLETE
+        when: 'getting cm handle details for a given cm handle id from ncmp service'
+            def result = objectUnderTest.getNcmpServiceCmHandle(cmHandleRef)
+        then: 'the result is a ncmpServiceCmHandle'
+            assert result.class == NcmpServiceCmHandle.class
+        and: 'the cm handle contains the cm handle id'
+            assert result.cmHandleId == 'some-cm-handle'
+        and: 'the cm handle contains the alternate id'
+            assert result.alternateId == 'some-alternate-id'
+        and: 'the cm handle contains the module-set-tag'
+            assert result.moduleSetTag == 'some-module-set-tag'
+        and: 'the cm handle contains the DMI Properties'
+            assert result.dmiProperties ==[ Book:'Romance Novel' ]
+        and: 'the cm handle contains the public Properties'
+            assert result.publicProperties == [ "Public Book":'Public Romance Novel' ]
+        and: 'the cm handle contains the cm handle composite state'
+            assert result.compositeState == compositeState
+        and: 'the cm handle contains the trust level from the cache'
+            assert result.currentTrustLevel == TrustLevel.COMPLETE
+        where: 'following cm handle reference is used'
+            scenario                              | cmHandleRef
+            'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
+            'Cm Handle Reference as alternate-id' | 'some-alternate-id'
+    }
+
+    def 'Get cm handle public properties using #scenario'() {
+        given: 'a yang modelled cm handle'
+            def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
+            def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
+            def cmHandleId = 'some-cm-handle'
+            def alternateId = 'some-alternate-id'
+            def yangModelCmHandle = new YangModelCmHandle(id:cmHandleId, alternateId: alternateId, dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
+        and: 'we have corresponding cm handle for the cm handle reference'
+            1 * mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> cmHandleId
+        and: 'the system returns this yang modelled cm handle'
+            1 * mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle
+        when: 'getting cm handle public properties for a given cm handle reference from ncmp service'
+            def result = objectUnderTest.getCmHandlePublicProperties(cmHandleRef)
+        then: 'the result returns the correct data'
+            assert result == [ 'public prop' : 'some public prop' ]
+        where: 'following cm handle reference is used'
+            scenario                              | cmHandleRef
+            'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
+            'Cm Handle Reference as alternate-id' | 'some-alternate-id'
+    }
+
+    def 'Get cm handle composite state using #scenario'() {
+        given: 'a yang modelled cm handle'
+            def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
+                lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
+                lastUpdateTime: 'some-timestamp',
+                dataSyncEnabled: false,
+                dataStores: dataStores())
+            def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
+            def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
+            def cmHandleId = 'some-cm-handle'
+            def alternateId = 'some-alternate-id'
+            def yangModelCmHandle = new YangModelCmHandle(id:cmHandleId, alternateId: alternateId, dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
+        and: 'we have corresponding cm handle for the cm handle reference'
+            1 * mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> cmHandleId
+        and: 'the system returns this yang modelled cm handle'
+            1 * mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle
+        when: 'getting cm handle composite state for a given cm handle id from ncmp service'
+            def result = objectUnderTest.getCmHandleCompositeState(cmHandleRef)
+        then: 'the result returns the correct data'
+            assert result == compositeState
+        where: 'following cm handle reference is used'
+            scenario                              | cmHandleRef
+            'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
+            'Cm Handle Reference as alternate-id' | 'some-alternate-id'
+    }
+
+    def 'Execute cm handle id search'() {
+        given: 'valid CmHandleQueryApiParameters input'
+            def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
+            def conditionApiProperties = new ConditionApiProperties()
+            conditionApiProperties.conditionName = 'hasAllModules'
+            conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
+            cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
+        and: 'query cm handle method return with a data node list'
+            mockParameterizedCmHandleQueryService.queryCmHandleIds(
+                spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
+                >> ['cm-handle-id-1']
+        when: 'execute cm handle search is called'
+            def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
+        then: 'result is the same collection as returned by the CPS Data Service'
+            assert result == ['cm-handle-id-1']
+    }
+
+    def 'Getting module definitions by module for a given #scenario'() {
+        when: 'get module definitions is performed with module name and cm handle reference'
+            objectUnderTest.getModuleDefinitionsByCmHandleAndModule(cmHandleRef, 'some-module', '2021-08-04')
+        then: 'alternate id matcher returns some cm handle id for a given cm handle reference'
+            mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
+        and: 'ncmp inventory persistence service is invoked once with correct parameters'
+            1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
+        where: 'following cm handle reference is used'
+            scenario                              | cmHandleRef
+            'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
+            'Cm Handle Reference as alternate-id' | 'some-alternate-id'
+    }
+
+    def 'Getting module definitions for a given #scenario'() {
+        when: 'get module definitions is performed with cm handle reference'
+            objectUnderTest.getModuleDefinitionsByCmHandleReference(cmHandleRef)
+        then: 'alternate id matcher returns some cm handle id for a given cm handle reference'
+            mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
+        then: 'ncmp inventory persistence service is invoked once with correct parameter'
+            1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
+        where: 'following cm handle reference is used'
+            scenario                              | cmHandleRef
+            'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
+            'Cm Handle Reference as alternate-id' | 'some-alternate-id'
+    }
+
+    def 'Execute cm handle search'() {
+        given: 'valid CmHandleQueryApiParameters input'
+            def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
+            def conditionApiProperties = new ConditionApiProperties()
+            conditionApiProperties.conditionName = 'hasAllModules'
+            conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
+            cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
+        and: 'query cm handle method returns two cm handles'
+            mockParameterizedCmHandleQueryService.queryCmHandles(
+                spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
+                >> [new YangModelCmHandle(id: 'ch-0', dmiProperties: [], publicProperties: []),
+                    new YangModelCmHandle(id: 'ch-1', dmiProperties: [], publicProperties: [])]
+        and: 'a trust level for cm handles'
+            mockTrustLevelManager.getEffectiveTrustLevel(*_) >> TrustLevel.COMPLETE
+        when: 'execute cm handle search is called'
+            def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
+        then: 'result consists of the two cm handles returned by the CPS Data Service'
+            assert result.size() == 2
+            assert result[0].cmHandleId == 'ch-0'
+            assert result[1].cmHandleId == 'ch-1'
+        and: 'cm handles have trust level'
+            assert result[0].currentTrustLevel == TrustLevel.COMPLETE
+            assert result[1].currentTrustLevel == TrustLevel.COMPLETE
+    }
+
+    def 'Set Cm Handle Data Sync flag.'() {
+        when: 'setting data sync enabled flag'
+            objectUnderTest.setDataSyncEnabled('ch-1',true)
+        then: 'call is delegated to the cm handle registration service'
+            mockCmHandleRegistrationService.setDataSyncEnabled('ch-1', true)
+    }
+
+    def dataStores() {
+        CompositeState.DataStores.builder().operationalDataStore(CompositeState.Operational.builder()
+                .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
+                .lastSyncTime('some-timestamp').build()).build()
+    }
+}
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
+package org.onap.cps.ncmp.impl.inventory
 
 import org.onap.cps.cpspath.parser.PathParsingException
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueriesImpl
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.exceptions.DataInUseException
 import org.onap.cps.spi.exceptions.DataValidationException
@@ -36,16 +30,18 @@ import org.onap.cps.spi.model.ConditionProperties
 import org.onap.cps.spi.model.DataNode
 import spock.lang.Specification
 
-class NetworkCmProxyCmHandleQueryServiceSpec extends Specification {
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
+
+class ParameterizedCmHandleQueryServiceSpec extends Specification {
 
-    def cmHandleQueries = Mock(CmHandleQueries)
-    def partiallyMockedCmHandleQueries = Spy(CmHandleQueries)
+    def cmHandleQueries = Mock(CmHandleQueryService)
+    def partiallyMockedCmHandleQueries = Spy(CmHandleQueryService)
     def mockInventoryPersistence = Mock(InventoryPersistence)
 
     def dmiRegistry = new DataNode(xpath: NCMP_DMI_REGISTRY_PARENT, childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4']))
 
-    def objectUnderTest = new NetworkCmProxyCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence)
-    def objectUnderTestWithPartiallyMockedQueries = new NetworkCmProxyCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence)
+    def objectUnderTest = new ParameterizedCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence)
+    def objectUnderTestWithPartiallyMockedQueries = new ParameterizedCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence)
 
     def 'Query cm handle ids with cpsPath.'() {
         given: 'a cmHandleWithCpsPath condition property'
@@ -60,6 +56,19 @@ class NetworkCmProxyCmHandleQueryServiceSpec extends Specification {
             assert result == ['some-cmhandle-id'] as Set
     }
 
+    def 'Query cm handle where  cps path itself is ancestor axis.'() {
+        given: 'a cmHandleWithCpsPath condition property'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        and: 'the query get the cm handle data nodes excluding all descendants returns a datanode'
+            cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])]
+        when: 'the query is executed for cm handle ids'
+            def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters)
+        then: 'the correct expected cm handles ids are returned'
+            assert result == ['some-cmhandle-id'] as Set
+    }
+
     def 'Cm handle ids query with error: #scenario.'() {
         given: 'a cmHandleWithCpsPath condition property'
             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
@@ -129,10 +138,10 @@ class NetworkCmProxyCmHandleQueryServiceSpec extends Specification {
         and: 'the inventory service is called with teh correct if and returns a yang model cm handle'
             1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >>
                 [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])]
-        and: 'the expected cm handle(s) are returned as NCMP Service cm handles'
-            assert result[0] instanceof NcmpServiceCmHandle
+        and: 'the expected cm handle(s) are returned as Yang Model cm handles'
+            assert result[0] instanceof YangModelCmHandle
             assert result.size() == 1
-            assert result[0].dmiProperties == [name:'value']
+            assert result[0].dmiProperties.size() == 1
     }
 
     def 'Query cm handle ids when the query is empty.'() {
@@ -155,7 +164,7 @@ class NetworkCmProxyCmHandleQueryServiceSpec extends Specification {
             def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
         then: 'the correct cm handles are returned'
             assert result.size() == 4
-            assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4')
+            assert result.id.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4')
     }
 
     def 'Query CMHandleId with #scenario.' () {
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- * Copyright (c) 2022 Nordix Foundation.
+ * Copyright (c) 2022-2024 Nordix Foundation.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils
+package org.onap.cps.ncmp.impl.inventory.models
 
 import spock.lang.Specification
 
 class InventoryQueryConditionsSpec extends Specification {
 
     def 'Inventory query condition names.'() {
-        expect: '3 conditions with the correct names'
-            assert InventoryQueryConditions.ALL_CONDITION_NAMES.size() == 3
+        expect: '4 conditions with the correct names'
+            assert InventoryQueryConditions.ALL_CONDITION_NAMES.size() == 4
             assert InventoryQueryConditions.ALL_CONDITION_NAMES.containsAll('hasAllProperties',
                                                                             'hasAllAdditionalProperties',
-                                                                            'cmHandleWithDmiPlugin')
+                                                                            'cmHandleWithDmiPlugin',
+                                                                            'cmHandleWithCpsPath')
     }
-
-
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.yangmodels
+package org.onap.cps.ncmp.impl.inventory.models
 
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
 import spock.lang.Specification
 
-import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA
-import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL
 
 class YangModelCmHandleSpec extends Specification {
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync.executor
+package org.onap.cps.ncmp.impl.inventory.sync
+
 
-import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import spock.lang.Specification
+
 import java.util.concurrent.TimeoutException
 import java.util.function.Supplier
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
+package org.onap.cps.ncmp.impl.inventory.sync
 
 import com.hazelcast.map.IMap
 import org.onap.cps.api.CpsDataService
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
+
 class DataSyncWatchdogSpec extends Specification {
 
     def mockInventoryPersistence = Mock(InventoryPersistence)
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations
+package org.onap.cps.ncmp.impl.inventory.sync
 
 import com.fasterxml.jackson.core.JsonProcessingException
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.impl.dmi.DmiOperationsBaseSpec
+import org.onap.cps.ncmp.impl.dmi.DmiProperties
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
@@ -34,12 +36,16 @@ import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
 import spock.lang.Shared
 
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
+import static org.onap.cps.ncmp.api.data.models.OperationType.READ
+import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL
 
 @SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations])
+@ContextConfiguration(classes = [DmiProperties, DmiModelOperations])
 class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
 
+    def expectedModulesUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/modules', ['cmHandleId': cmHandleId])
+    def expectedModuleResourcesUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/moduleResources', ['cmHandleId': cmHandleId])
+
     @Shared
     def newModuleReferences = [new ModuleReference('mod1','A'), new ModuleReference('mod2','X')]
 
@@ -56,10 +62,8 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
             mockYangModelCmHandleRetrieval([])
         and: 'a positive response from DMI service when it is called with the expected parameters'
             def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']]
-            def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules"
             def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK)
-            mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER)
-                    >> responseFromDmi
+            mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModulesUrlTemplateWithVariables, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi
         when: 'get module references is called'
             def result = objectUnderTest.getModuleReferences(yangModelCmHandle)
         then: 'the result consists of expected module references'
@@ -72,7 +76,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         and: 'any response from DMI service when it is called with the expected parameters'
             // TODO (toine): production code ignores any error code from DMI, this should be improved in future
             def responseFromDmi = new ResponseEntity(bodyAsMap, HttpStatus.NO_CONTENT)
-            mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi
+            mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi
         when: 'get module references is called'
             def result = objectUnderTest.getModuleReferences(yangModelCmHandle)
         then: 'the result is empty'
@@ -90,7 +94,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
             mockYangModelCmHandleRetrieval(dmiProperties)
         and: 'a positive response from DMI service when it is called with tha expected parameters'
             def responseFromDmi = new ResponseEntity<String>(HttpStatus.OK)
-            mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules",
+            mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModulesUrlTemplateWithVariables,
                     '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi
         when: 'a get module references is called'
             def result = objectUnderTest.getModuleReferences(yangModelCmHandle)
@@ -109,7 +113,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
             def responseFromDmi = new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'],
                                                       [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK)
             def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
-            mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources",
+            mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables,
                     '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi
         when: 'get new yang resources from DMI service'
             def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences)
@@ -125,7 +129,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         and: 'a positive response from DMI service when it is called with tha expected parameters'
             // TODO (toine): production code ignores any error code from DMI, this should be improved in future
             def responseFromDmi = new ResponseEntity(responseFromDmiBody, HttpStatus.NO_CONTENT)
-            mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi
+            mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi
         when: 'get new yang resources from DMI service'
             def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences)
         then: 'the result is empty'
@@ -141,7 +145,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
             mockYangModelCmHandleRetrieval(dmiProperties)
         and: 'a positive response from DMI service when it is called with the expected moduleSetTag, modules and properties'
             def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK)
-            mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources",
+            mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables,
                     '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}',
                     READ, NO_AUTH_HEADER) >> responseFromDmi
         when: 'get new yang resources from DMI service'
@@ -159,17 +163,17 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
             mockYangModelCmHandleRetrieval([], moduleSetTag)
         and: 'a positive response from DMI service when it is called with the expected moduleSetTag'
             def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK)
-            mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources",
-                '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}',
-                READ, NO_AUTH_HEADER) >> responseFromDmi
+            mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables,
+                '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi
         when: 'get new yang resources from DMI service'
             def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences)
         then: 'the result is the response from DMI service'
             assert result == [mod1:'some yang source']
         where: 'the following Module Set Tags are used'
-            scenario                 | moduleSetTag    || expectedModuleSetTagInRequest
-            'Without module set tag' | ''              || ''
-            'With module set tag'    | 'moduleSetTag1' || '"moduleSetTag":"moduleSetTag1",'
+            scenario                               | moduleSetTag       || expectedModuleSetTagInRequest
+            'Without module set tag'               | ''                 || ''
+            'With module set tag'                  | 'moduleSetTag1'    || '"moduleSetTag":"moduleSetTag1",'
+            'Special characters in module set tag' | 'module:set#tag$2' || '"moduleSetTag":"module:set#tag$2",'
     }
 
     def 'Retrieving yang resources from DMI with no module references.'() {
@@ -180,7 +184,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         then: 'no resources are returned'
             assert result == [:]
         and: 'no request is sent to DMI'
-            0 * mockDmiRestClient.postOperationWithJsonData(*_)
+            0 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_)
     }
 
     def 'Retrieving yang resources from DMI with null DMI properties.'() {
@@ -204,5 +208,4 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         and: 'the message indicates a parsing error'
             exceptionThrown.message.toLowerCase().contains('parsing error')
     }
-
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync
-
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED
+package org.onap.cps.ncmp.impl.inventory.sync
 
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
 import ch.qos.logback.core.read.ListAppender
-import org.slf4j.LoggerFactory
-import org.springframework.context.annotation.AnnotationConfigApplicationContext
 import com.fasterxml.jackson.databind.JsonNode
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
+import org.onap.cps.ncmp.impl.data.DmiDataOperations
+import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.utils.JsonObjectMapper
+import org.slf4j.LoggerFactory
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
 import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
 import spock.lang.Specification
-import java.time.OffsetDateTime
-import java.time.format.DateTimeFormatter
 import java.util.stream.Collectors
 
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED
+
 class ModuleOperationsUtilsSpec extends Specification{
 
-    def mockCmHandleQueries = Mock(CmHandleQueries)
+    def mockCmHandleQueries = Mock(CmHandleQueryService)
 
     def mockDmiDataOperations = Mock(DmiDataOperations)
 
@@ -60,17 +56,12 @@ class ModuleOperationsUtilsSpec extends Specification{
 
     def objectUnderTest = new ModuleOperationsUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper)
 
-    def static neverUpdatedBefore = '1900-01-01T00:00:00.000+0100'
-
-    def static now = OffsetDateTime.now()
-
-    def static nowAsString = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now)
-
     def static dataNode = new DataNode(leaves: ['id': 'cm-handle-123'])
 
     def applicationContext = new AnnotationConfigApplicationContext()
 
     def logger = (Logger) LoggerFactory.getLogger(ModuleOperationsUtils)
+
     def loggingListAppender
 
     void setup() {
@@ -103,7 +94,7 @@ class ModuleOperationsUtilsSpec extends Specification{
         given: 'A locked state'
             def compositeState = new CompositeState(lockReason: lockReason)
         when: 'update cm handle details and attempts is called'
-            objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_SYNC_FAILED, 'new error message')
+            objectUnderTest.updateLockReasonWithAttempts(compositeState, MODULE_SYNC_FAILED, 'new error message')
         then: 'the composite state lock reason and details are updated'
             assert compositeState.lockReason.lockReasonCategory == MODULE_SYNC_FAILED
             assert compositeState.lockReason.details.contains(expectedDetails)
@@ -118,14 +109,14 @@ class ModuleOperationsUtilsSpec extends Specification{
             def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED)
                 .withLockReason(MODULE_UPGRADE, "Upgrade to ModuleSetTag: " + moduleSetTag).build()
         when: 'update cm handle details'
-            objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_UPGRADE_FAILED, 'new error message')
+            objectUnderTest.updateLockReasonWithAttempts(compositeState, MODULE_UPGRADE_FAILED, 'new error message')
         then: 'the composite state lock reason and details are updated'
             assert compositeState.lockReason.lockReasonCategory == MODULE_UPGRADE_FAILED
-            assert compositeState.lockReason.details.contains("Upgrade to ModuleSetTag: " + expectedDetails)
+            assert compositeState.lockReason.details.contains(expectedDetails)
         where:
             scenario               | moduleSetTag       || expectedDetails
-            'a module set tag'     | 'someModuleSetTag' || 'someModuleSetTag'
-            'empty module set tag' | ''                 || ''
+            'a module set tag'     | 'someModuleSetTag' || 'Upgrade to ModuleSetTag: someModuleSetTag'
+            'empty module set tag' | ''                 || 'Attempt'
     }
 
     def 'Get all locked cm-Handles where lock reasons are model sync failed or upgrade'() {
@@ -135,49 +126,9 @@ class ModuleOperationsUtilsSpec extends Specification{
         when: 'get locked Misbehaving cm handle is called'
             def result = objectUnderTest.getCmHandlesThatFailedModelSyncOrUpgrade()
         then: 'the returned cm handle collection is the correct size'
-            result.size() == 1
+            assert result.size() == 1
         and: 'the correct cm handle is returned'
-            result[0].id == 'cm-handle-123'
-    }
-
-    def 'Retry Locked Cm-Handle where the last update time is #scenario'() {
-        given: 'Last update was #lastUpdateMinutesAgo minutes ago (-1 means never)'
-            def lastUpdatedTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now.minusMinutes(lastUpdateMinutesAgo))
-            if (lastUpdateMinutesAgo < 0 ) {
-                lastUpdatedTime = neverUpdatedBefore
-            }
-        when: 'checking to see if cm handle is ready for retry'
-         def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder()
-                .withLockReason(MODULE_SYNC_FAILED, lockDetails)
-                .withLastUpdatedTime(lastUpdatedTime).build())
-        then: 'retry is only attempted when expected'
-            assert result == retryExpected
-        and: 'logs contain related information'
-            def logs = loggingListAppender.list.toString()
-            assert logs.contains(logReason)
-        where: 'the following parameters are used'
-            scenario                                    | lastUpdateMinutesAgo | lockDetails                     | logReason                               || retryExpected
-            'never attempted before'                    | -1                   | 'Fist attempt:'                 | 'First Attempt:'                        || true
-            '1st attempt, last attempt > 2 minute ago'  | 3                    | 'Attempt #1 failed: some error' | 'Retry due now'                         || true
-            '2nd attempt, last attempt < 4 minutes ago' | 1                    | 'Attempt #2 failed: some error' | 'Time until next attempt is 3 minutes:' || false
-            '2nd attempt, last attempt > 4 minutes ago' | 5                    | 'Attempt #2 failed: some error' | 'Retry due now'                         || true
-    }
-
-    def 'Retry Locked Cm-Handle with lock reasons (category) #lockReasonCategory'() {
-        when: 'checking to see if cm handle is ready for retry'
-            def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder()
-                .withLockReason(lockReasonCategory, 'some details')
-                .withLastUpdatedTime(nowAsString).build())
-        then: 'verify retry attempts'
-            assert !result
-        and: 'logs contain related information'
-            def logs = loggingListAppender.list.toString()
-            assert logs.contains(logReason)
-        where: 'the following lock reasons occurred'
-            scenario             | lockReasonCategory    || logReason
-            'module upgrade'     | MODULE_UPGRADE_FAILED || 'First Attempt:'
-            'module sync failed' | MODULE_SYNC_FAILED    || 'First Attempt:'
-            'lock misbehaving'   | LOCKED_MISBEHAVING    || 'Locked for other reason'
+            assert result[0].id == 'cm-handle-123'
     }
 
     def 'Get a Cm-Handle where #scenario'() {
@@ -197,19 +148,25 @@ class ModuleOperationsUtilsSpec extends Specification{
             'all Cm-Handle synchronized'               | []                      | false            || 0                    | [] as Set
     }
 
-    def 'Get resource data through DMI Operations #scenario'() {
-        given: 'the inventory persistence service returns a collection of data nodes'
+    def 'Retrieve resource data from DMI operations for #scenario'() {
+        given: 'a JSON string representing the resource data'
             def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}'
-            JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString);
-            def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK)
-            mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName, 'cm-handle-123', _) >> responseEntity
+            JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString)
+        and: 'DMI operations are mocked to return a response based on the scenario'
+            def responseEntity = new ResponseEntity<>(statusCode == HttpStatus.OK ? jsonNode : null, statusCode)
+            mockDmiDataOperations.getAllResourceDataFromDmi('cm-handle-123', _) >> responseEntity
         when: 'get resource data is called'
             def result = objectUnderTest.getResourceData('cm-handle-123')
-        then: 'the returned data is correct'
-            result == jsonString
+        then: 'the returned data matches the expected result'
+            assert result == expectedResult
+        where:
+            scenario                              | statusCode                       | expectedResult
+            'successful response'                 | HttpStatus.OK                    | '{"stores:bookstore":{"categories":[{"code":"01"}]}}'
+            'response with not found status'      | HttpStatus.NOT_FOUND             | null
+            'response with internal server error' | HttpStatus.INTERNAL_SERVER_ERROR | null
     }
 
-    def 'Extract module set tag and number of attempt when lock reason contains #scenario'() {
+        def 'Extract module set tag and number of attempt when lock reason contains #scenario'() {
         expect: 'lock reason details are extracted correctly'
             def result = objectUnderTest.getLockedCompositeStateDetails(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, lockReasonDetails).build().lockReason)
         and: 'the result contains the correct moduleSetTag'
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync
+package org.onap.cps.ncmp.impl.inventory.sync
 
 import org.onap.cps.api.CpsAnchorService
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE
-
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.spi.model.DataNode
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.spi.model.DataNodeBuilder
-import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.spi.CascadeDeleteAllowed
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE
+
 class ModuleSyncServiceSpec extends Specification {
 
     def mockCpsModuleService = Mock(CpsModuleService)
     def mockDmiModelOperations = Mock(DmiModelOperations)
     def mockCpsAnchorService = Mock(CpsAnchorService)
-    def mockCmHandleQueries = Mock(CmHandleQueries)
+    def mockCmHandleQueries = Mock(CmHandleQueryService)
     def mockCpsDataService = Mock(CpsDataService)
     def mockJsonObjectMapper = Mock(JsonObjectMapper)
 
     def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService,
-            mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper)
+            mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper)
 
     def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-    def static cmHandleWithModuleSetTag = new DataNodeBuilder()
-            .withXpath("/dmi-registry/cm-handles[@id='otherId']")
-            .withLeaves(['id': 'otherId', 'module-set-tag': 'tag-1'])
-            .withAnchor('otherId').build()
 
     def 'Sync model for a NEW cm handle using module set tags: #scenario.'() {
         given: 'a cm handle state to be synced'
@@ -72,8 +64,8 @@ class ModuleSyncServiceSpec extends Specification {
             mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferences) >> newModuleNameContentToMap
         and: 'the module service identifies #identifiedNewModuleReferences.size() new modules'
             mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> identifiedNewModuleReferences
-        and: 'system contains other cm handle with "same tag" (that is READY)'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag
+        and: 'the service returns a list of module references when queried with the specified attributes'
+            mockCpsModuleService.getModuleReferencesByAttribute(*_) >> existingModuleReferences
         when: 'module sync is triggered'
             objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle)
         then: 'create schema set from module is invoked with correct parameters'
@@ -81,10 +73,10 @@ class ModuleSyncServiceSpec extends Specification {
         and: 'anchor is created with the correct parameters'
             1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1')
         where: 'the following parameters are used'
-            scenario                  | existingModuleResourcesInCps         | identifiedNewModuleReferences         | newModuleNameContentToMap     | moduleSetTag | existingCmHandlesWithSameTag
-            'one new module, new tag' | [['module2': '2'], ['module3': '3']] | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | ''           | []
-            'no new module, new tag'  | [['module1': '1'], ['module2': '2']] | []                                    | [:]                           | 'new-tag-1'  | []
-            'same tag'                | [['module1': '1'], ['module2': '2']] | []                                    | [:]                           | 'same-tag'   | [cmHandleWithModuleSetTag]
+            scenario                  | identifiedNewModuleReferences         | newModuleNameContentToMap     | moduleSetTag | existingModuleReferences
+            'one new module, new tag' | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | ''           | []
+            'no new module, new tag'  | []                                    | [:]                           | 'new-tag-1'  | []
+            'same tag'                | []                                    | [:]                           | 'same-tag'   | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')]
     }
 
     def 'Upgrade model for an existing cm handle with Module Set Tag where the modules are #scenario'() {
@@ -103,8 +95,8 @@ class ModuleSyncServiceSpec extends Specification {
             mockCpsModuleService.identifyNewModuleReferences(_) >> []
         and: 'CPS-Core returns list of existing module resources for TBD'
             mockCpsModuleService.getYangResourcesModuleReferences(*_) >> [ new ModuleReference('module1','1') ]
-        and: 'system contains #existingCmHandlesWithSameTag.size() cm handles with same tag'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag
+        and: 'the service returns a list of module references when queried with the specified attributes'
+            mockCpsModuleService.getModuleReferencesByAttribute(*_) >> existingModuleReferences
         and: 'the other cm handle is a state ready'
             mockCmHandleQueries.cmHandleHasState('otherId', CmHandleState.READY) >> true
         when: 'module sync is triggered'
@@ -116,9 +108,9 @@ class ModuleSyncServiceSpec extends Specification {
         and: 'No anchor is created for the upgraded cm handle'
             0 * mockCpsAnchorService.createAnchor(*_)
         where: 'the following parameters are used'
-            scenario      | existingCmHandlesWithSameTag
+            scenario      | existingModuleReferences
             'new'         | []
-            'in database' | [cmHandleWithModuleSetTag]
+            'in database' | [new ModuleReference('module1', '1')]
     }
 
     def 'upgrade model for a existing cm handle'() {
@@ -132,9 +124,8 @@ class ModuleSyncServiceSpec extends Specification {
         and: 'the module service returns some module references'
             def moduleReferences = [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')]
             mockCpsModuleService.getYangResourcesModuleReferences(*_)>> moduleReferences
-        and: 'a cm handle with the same moduleSetTag can be found in the registry'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1'],
-                    childDataNodes: [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']/state', leaves: ['cm-handle-state': 'READY'])])]
+        and: 'the service returns a list of module references when queried with the specified attributes'
+            mockCpsModuleService.getModuleReferencesByAttribute(*_) >> moduleReferences
         when: 'module upgrade is triggered'
             objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle)
         then: 'the upgrade is delegated to the module service (with the correct parameters)'
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync
-
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED
+package org.onap.cps.ncmp.impl.inventory.sync
 
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
@@ -31,18 +28,21 @@ import ch.qos.logback.core.read.ListAppender
 import com.hazelcast.config.Config
 import com.hazelcast.instance.impl.HazelcastInstanceFactory
 import com.hazelcast.map.IMap
-import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler
 import org.onap.cps.spi.model.DataNode
 import org.slf4j.LoggerFactory
 import spock.lang.Specification
 import java.util.concurrent.atomic.AtomicInteger
 
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED
+
 class ModuleSyncTasksSpec extends Specification {
 
     def logger = Spy(ListAppender<ILoggingEvent>)
@@ -95,70 +95,52 @@ class ModuleSyncTasksSpec extends Specification {
             assert batchCount.get() == 4
     }
 
-    def 'Module Sync ADVISED cm handle with failure during sync.'() {
-        given: 'a cm handle in an ADVISED state'
-            def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.ADVISED)
-        and: 'the inventory persistence cm handle returns a ADVISED state for the cm handle'
-            def cmHandleState = new CompositeState(cmHandleState: CmHandleState.ADVISED)
-            1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> cmHandleState
-        and: 'module sync service attempts to sync the cm handle and throws an exception'
-            1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') }
+    def 'Handle CM handle failure during #scenario and log MODULE_UPGRADE lock reason'() {
+        given: 'a CM handle in LOCKED state with a specific lock reason'
+            def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED)
+            def expectedCmHandleState = new CompositeState(cmHandleState: CmHandleState.LOCKED, lockReason: CompositeState
+                    .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build())
+            1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState
+        and: 'module sync service attempts to sync/upgrade the CM handle and throws an exception'
+            mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { throw new Exception('some exception') }
+            mockModuleSyncService.syncAndUpgradeSchemaSet(_) >> { throw new Exception('some exception') }
         when: 'module sync is executed'
             objectUnderTest.performModuleSync([cmHandle], batchCount)
-        then: 'update lock reason, details and attempts is invoked'
-            1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, MODULE_SYNC_FAILED, 'some exception')
+        then: 'lock reason is updated with number of attempts'
+            1 * mockSyncUtils.updateLockReasonWithAttempts(expectedCmHandleState, expectedLockReasonCategory, 'some exception')
         and: 'the state handler is called to update the state to LOCKED'
             1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args ->
                 assertBatch(args, ['cm-handle'], CmHandleState.LOCKED)
             }
         and: 'batch count is decremented by one'
             assert batchCount.get() == 4
-    }
-
-    def 'Failed cm handle during #scenario.'() {
-        given: 'a cm handle in LOCKED state'
-            def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED)
-        and: 'the inventory persistence cm handle returns a LOCKED state with reason for the cm handle'
-            def expectedCmHandleState = new CompositeState(cmHandleState: cmHandleState, lockReason: CompositeState
-                .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build())
-            1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState
-        and: 'module sync service attempts to sync/upgrade the cm handle and throws an exception'
-            mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') }
-            mockModuleSyncService.syncAndUpgradeSchemaSet(*_) >> { throw new Exception('some exception') }
-        when: 'module sync is executed'
-            objectUnderTest.performModuleSync([cmHandle], batchCount)
-        then: 'update lock reason, details and attempts is invoked'
-            1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(expectedCmHandleState, expectedLockReasonCategory, 'some exception')
         where:
-            scenario         | cmHandleState        | lockReasonCategory    | lockReasonDetails                              || expectedLockReasonCategory
-            'module upgrade' | CmHandleState.LOCKED | MODULE_UPGRADE_FAILED | 'Upgrade to ModuleSetTag: some-module-set-tag' || MODULE_UPGRADE_FAILED
-            'module sync'    | CmHandleState.LOCKED | MODULE_SYNC_FAILED    | 'some lock details'                            || MODULE_SYNC_FAILED
+            scenario         | lockReasonCategory    | lockReasonDetails                              || expectedLockReasonCategory
+            'module sync'    | MODULE_SYNC_FAILED    | 'some lock details'                            || MODULE_SYNC_FAILED
+            'module upgrade' | MODULE_UPGRADE_FAILED | 'Upgrade to ModuleSetTag: some-module-set-tag' || MODULE_UPGRADE_FAILED
+            'module upgrade' | MODULE_UPGRADE        | 'Upgrade in progress'                          || MODULE_UPGRADE_FAILED
     }
 
+
     def 'Reset failed CM Handles #scenario.'() {
         given: 'cm handles in an locked state'
             def lockedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED)
-                    .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, '').withLastUpdatedTimeNow().build()
+                .withLockReason(MODULE_SYNC_FAILED, '').withLastUpdatedTimeNow().build()
             def yangModelCmHandle1 = new YangModelCmHandle(id: 'cm-handle-1', compositeState: lockedState)
             def yangModelCmHandle2 = new YangModelCmHandle(id: 'cm-handle-2', compositeState: lockedState)
-            def expectedCmHandleStatePerCmHandle = [(yangModelCmHandle1): CmHandleState.ADVISED]
+            def expectedCmHandleStatePerCmHandle
+                    = [(yangModelCmHandle1): CmHandleState.ADVISED, (yangModelCmHandle2): CmHandleState.ADVISED]
         and: 'clear in progress map'
             resetModuleSyncStartedOnCmHandles(moduleSyncStartedOnCmHandles)
         and: 'add cm handle entry into progress map'
             moduleSyncStartedOnCmHandles.put('cm-handle-1', 'started')
             moduleSyncStartedOnCmHandles.put('cm-handle-2', 'started')
-        and: 'sync utils retry locked cm handle returns #isReadyForRetry'
-            mockSyncUtils.needsModuleSyncRetryOrUpgrade(lockedState) >>> isReadyForRetry
         when: 'resetting failed cm handles'
             objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2])
         then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry'
-            expectedNumberOfInvocationsToUpdateCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle)
-        and: 'after reset performed size of in progress map'
-            assert moduleSyncStartedOnCmHandles.size() == inProgressMapSize
-        where:
-            scenario                        | isReadyForRetry | inProgressMapSize || expectedNumberOfInvocationsToUpdateCmHandleState
-            'retry locked cm handle'        | [true, false]   | 1                 || 1
-            'do not retry locked cm handle' | [false, false]  | 2                 || 0
+            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle)
+        and: 'after reset performed progress map is empty'
+            assert moduleSyncStartedOnCmHandles.size() == 0
     }
 
     def 'Module Sync ADVISED cm handle without entry in progress map.'() {
@@ -188,6 +170,24 @@ class ModuleSyncTasksSpec extends Specification {
             assert loggingEvent.formattedMessage == 'ch-1 removed from in progress map'
     }
 
+    def 'Sync and upgrade CM handle if in upgrade state for #scenario'() {
+        given: 'a CM handle in an upgrade state'
+            def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED)
+            def compositeState = new CompositeState(lockReason: CompositeState.LockReason.builder().lockReasonCategory(lockReasonCategory).build())
+            1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> compositeState
+        when: 'module sync is executed'
+            objectUnderTest.performModuleSync([cmHandle], batchCount)
+        then: 'the module sync service should attempt to sync and upgrade the CM handle'
+            1 * mockModuleSyncService.syncAndUpgradeSchemaSet(_) >> { args ->
+                assert args[0].id == 'cm-handle'
+            }
+        where: 'the following lock reasons are used'
+            scenario                | lockReasonCategory
+            'module upgrade'        | MODULE_UPGRADE
+            'module upgrade failed' | MODULE_UPGRADE_FAILED
+    }
+
+
     def 'Remove non-existing cm handle id from hazelcast map'() {
         given: 'hazelcast map does not contains cm handle id'
             def result = moduleSyncStartedOnCmHandles.get('non-existing-cm-handle')
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.inventory.sync
+package org.onap.cps.ncmp.impl.inventory.sync
 
 import com.hazelcast.map.IMap
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor
-import java.util.concurrent.ArrayBlockingQueue
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.spi.model.DataNode
 import spock.lang.Specification
 
+import java.util.concurrent.ArrayBlockingQueue
+
 class ModuleSyncWatchdogSpec extends Specification {
 
     def mockSyncUtils = Mock(ModuleOperationsUtils)
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.config.embeddedcache
+package org.onap.cps.ncmp.impl.inventory.sync
 
 import com.hazelcast.config.Config
 import com.hazelcast.core.Hazelcast
@@ -29,6 +29,7 @@ import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.test.context.ContextConfiguration
 import spock.lang.Specification
 import spock.util.concurrent.PollingConditions
+
 import java.util.concurrent.BlockingQueue
 import java.util.concurrent.TimeUnit
 
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm
-
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.LOCKED
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY
-import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED
-
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
+package org.onap.cps.ncmp.impl.inventory.sync.lcm
+
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.LOCKED
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY
+import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED
+
 class LcmEventsCmHandleStateHandlerImplSpec extends Specification {
 
     def mockInventoryPersistence = Mock(InventoryPersistence)
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm
-
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING
-import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY
+package org.onap.cps.ncmp.impl.inventory.sync.lcm
 
 import org.mapstruct.factory.Mappers
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.events.lcm.v1.Values
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING
+import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY
+
 class LcmEventsCreatorSpec extends Specification {
 
     LcmEventHeaderMapper lcmEventsHeaderMapper = Mappers.getMapper(LcmEventHeaderMapper)
@@ -190,4 +190,4 @@ class LcmEventsCreatorSpec extends Specification {
             'blank target and existing values'           | ''                  | ''                | ''                   | ''                 | ''                             | ''
             'new target value and blank existing values' | ''                  | 'someAlternateId' | ''                   | 'someAlternateId'  | ''                             | 'someDataProducerIdentifier'
     }
-}
\ No newline at end of file
+}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm
+package org.onap.cps.ncmp.impl.inventory.sync.lcm
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.apache.kafka.clients.consumer.KafkaConsumer
 import org.apache.kafka.common.serialization.StringDeserializer
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
 import org.onap.cps.ncmp.events.lcm.v1.Event
 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.utils.events.MessagingBaseSpec
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.lcm
+package org.onap.cps.ncmp.impl.inventory.sync.lcm
 
+import static org.onap.cps.ncmp.events.lcm.v1.Values.CmHandleState.ADVISED
+import static org.onap.cps.ncmp.events.lcm.v1.Values.CmHandleState.READY
+
+import io.micrometer.core.instrument.Tag
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry
 import org.onap.cps.events.EventsPublisher
+import org.onap.cps.ncmp.events.lcm.v1.Event
 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent
 import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader
+import org.onap.cps.ncmp.events.lcm.v1.Values
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.kafka.KafkaException
 import spock.lang.Specification
@@ -31,14 +38,16 @@ class LcmEventsServiceSpec extends Specification {
 
     def mockLcmEventsPublisher = Mock(EventsPublisher)
     def mockJsonObjectMapper = Mock(JsonObjectMapper)
+    def meterRegistry = new SimpleMeterRegistry()
 
-    def objectUnderTest = new LcmEventsService(mockLcmEventsPublisher, mockJsonObjectMapper)
+    def objectUnderTest = new LcmEventsService(mockLcmEventsPublisher, mockJsonObjectMapper, meterRegistry)
 
     def 'Create and Publish lcm event where events are #scenario'() {
         given: 'a cm handle id, Lcm Event, and headers'
             def cmHandleId = 'test-cm-handle-id'
             def eventId = UUID.randomUUID().toString()
-            def lcmEvent = new LcmEvent(eventId: eventId, eventCorrelationId: cmHandleId)
+            def event = getEventWithCmHandleState(ADVISED, READY)
+            def lcmEvent = new LcmEvent(event: event, eventId: eventId, eventCorrelationId: cmHandleId)
         and: 'we also have a lcm event header'
             def lcmEventHeader = new LcmEventHeader(eventId: eventId, eventCorrelationId: cmHandleId)
         and: 'notificationsEnabled is #notificationsEnabled and it will be true as default'
@@ -57,6 +66,16 @@ class LcmEventsServiceSpec extends Specification {
                     assert eventHeaders.get('eventCorrelationId') == cmHandleId
                 }
             }
+        and: 'metrics are recorded with correct tags'
+            def timer = meterRegistry.find('cps.ncmp.lcm.events.publish').timer()
+            if (notificationsEnabled) {
+                assert timer != null
+                assert timer.count() == expectedTimesMethodCalled
+                def tags = timer.getId().getTags()
+                assert tags.containsAll(Tag.of('oldCmHandleState', ADVISED.value()), Tag.of('newCmHandleState', READY.value()))
+            } else {
+                assert timer == null
+            }
         where: 'the following values are used'
             scenario   | notificationsEnabled || expectedTimesMethodCalled
             'enabled'  | true                 || 1
@@ -67,7 +86,8 @@ class LcmEventsServiceSpec extends Specification {
         given: 'a cm handle id and Lcm Event and notification enabled'
             def cmHandleId = 'test-cm-handle-id'
             def eventId = UUID.randomUUID().toString()
-            def lcmEvent = new LcmEvent(eventId: eventId, eventCorrelationId: cmHandleId)
+        and: 'event #event'
+            def lcmEvent = new LcmEvent(event: event, eventId: eventId, eventCorrelationId: cmHandleId)
             def lcmEventHeader = new LcmEventHeader(eventId: eventId, eventCorrelationId: cmHandleId)
             objectUnderTest.notificationsEnabled = true
         when: 'publisher set to throw an exception'
@@ -76,6 +96,35 @@ class LcmEventsServiceSpec extends Specification {
             objectUnderTest.publishLcmEvent(cmHandleId, lcmEvent, lcmEventHeader)
         then: 'the exception is just logged and not bubbled up'
             noExceptionThrown()
+        and: 'metrics are recorded with error tags'
+            def timer = meterRegistry.find('cps.ncmp.lcm.events.publish').timer()
+            assert timer != null
+            assert timer.count() == 1
+            def expectedTags = [Tag.of('oldCmHandleState', 'N/A'), Tag.of('newCmHandleState', 'N/A')]
+            def tags = timer.getId().getTags()
+            assert tags.containsAll(expectedTags)
+        where: 'the following values are used'
+            scenario                  | event
+            'without values'          | new Event()
+            'without cm handle state' | getEvent()
     }
 
+    def getEvent() {
+        def event = new Event()
+        def values = new Values()
+        event.setOldValues(values)
+        event.setNewValues(values)
+        event
+    }
+
+    def getEventWithCmHandleState(oldCmHandleState, newCmHandleState) {
+        def event = new Event()
+        def advisedCmHandleStateValues = new Values()
+        advisedCmHandleStateValues.setCmHandleState(oldCmHandleState)
+        event.setOldValues(advisedCmHandleStateValues)
+        def readyCmHandleStateValues = new Values()
+        readyCmHandleStateValues.setCmHandleState(newCmHandleState)
+        event.setNewValues(readyCmHandleStateValues)
+        return event
+    }
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.trustlevel
+package org.onap.cps.ncmp.impl.inventory.trustlevel
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
 import io.cloudevents.core.builder.CloudEventBuilder
 import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
 import org.onap.cps.ncmp.events.trustlevel.DeviceTrustLevel
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.boot.test.context.SpringBootTest
 import spock.lang.Specification
 
 @SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
-class DeviceHeartbeatConsumerSpec extends Specification {
+class DeviceTrustLevelMessageConsumerSpec extends Specification {
 
     def mockTrustLevelManager = Mock(TrustLevelManager)
 
-    def objectUnderTest = new DeviceHeartbeatConsumer(mockTrustLevelManager)
+    def objectUnderTest = new DeviceTrustLevelMessageConsumer(mockTrustLevelManager)
     def objectMapper = new ObjectMapper()
     def jsonObjectMapper = new JsonObjectMapper(objectMapper)
 
@@ -46,9 +47,9 @@ class DeviceHeartbeatConsumerSpec extends Specification {
             def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'sample-message-key', eventFromDmi)
             consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('ch-1'))
         when: 'the event is consumed'
-            objectUnderTest.heartbeatListener(consumerRecord)
+            objectUnderTest.deviceTrustLevelListener(consumerRecord)
         then: 'cm handles are stored with correct trust level'
-            1 * mockTrustLevelManager.handleUpdateOfDeviceTrustLevel('"ch-1"', TrustLevel.COMPLETE)
+            1 * mockTrustLevelManager.updateCmHandleTrustLevel('"ch-1"', TrustLevel.COMPLETE)
     }
 
     def createTrustLevelEvent(eventPayload) {
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability
+package org.onap.cps.ncmp.impl.inventory.trustlevel
 
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
+import org.onap.cps.ncmp.impl.dmi.DmiRestClient
+import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
+import reactor.core.publisher.Mono
 import spock.lang.Specification
 
-class DmiPluginWatchDogSpec extends Specification {
+class DmiPluginTrustLevelWatchDogSpec extends Specification {
 
     def mockDmiRestClient = Mock(DmiRestClient)
-    def mockNetworkCmProxyDataService = Mock(NetworkCmProxyDataService)
+    def mockCmHandleQueryService = Mock(CmHandleQueryService)
     def mockTrustLevelManager = Mock(TrustLevelManager)
     def trustLevelPerDmiPlugin = [:]
 
 
-    def objectUnderTest = new DmiPluginWatchDog(mockDmiRestClient,
-        mockNetworkCmProxyDataService,
-        mockTrustLevelManager,
-        trustLevelPerDmiPlugin)
+    def objectUnderTest = new DmiPluginTrustLevelWatchDog(mockDmiRestClient, mockCmHandleQueryService, mockTrustLevelManager, trustLevelPerDmiPlugin)
 
     def 'watch dmi plugin health status for #dmiHealhStatus'() {
         given: 'the cache has been initialised and "knows" about dmi-1'
             trustLevelPerDmiPlugin.put('dmi-1', dmiOldTrustLevel)
         and: 'dmi client returns health status #dmiHealhStatus'
-            mockDmiRestClient.getDmiHealthStatus('dmi-1') >> dmiHealhStatus
+            def urlTemplateParameters = new UrlTemplateParameters('dmi-1/actuator/health', [:])
+            mockDmiRestClient.getDmiHealthStatus(urlTemplateParameters) >> Mono.just(dmiHealhStatus)
         when: 'dmi watch dog method runs'
             objectUnderTest.checkDmiAvailability()
         then: 'the update delegated to manager'
-            numberOfCalls * mockTrustLevelManager.handleUpdateOfDmiTrustLevel('dmi-1', _, newDmiTrustLevel)
+            numberOfCalls * mockTrustLevelManager.updateDmi('dmi-1', _, newDmiTrustLevel)
         where: 'the following parameters are used'
             dmiHealhStatus | dmiOldTrustLevel    | newDmiTrustLevel    || numberOfCalls
             'UP'           | TrustLevel.COMPLETE | TrustLevel.COMPLETE || 0
@@ -56,5 +55,4 @@ class DmiPluginWatchDogSpec extends Specification {
             'UP'           | TrustLevel.NONE     | TrustLevel.COMPLETE || 1
             ''             | TrustLevel.COMPLETE | TrustLevel.NONE     || 1
     }
-
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.config.embeddedcache
+package org.onap.cps.ncmp.impl.inventory.trustlevel
 
 import com.hazelcast.config.Config
 import com.hazelcast.core.Hazelcast
-import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import spock.lang.Specification
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.trustlevel
+package org.onap.cps.ncmp.impl.inventory.trustlevel
 
-import org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient.AvcEventPublisher
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.TrustLevel
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.utils.events.CmAvcEventPublisher
 import spock.lang.Specification
 
 class TrustLevelManagerSpec extends Specification {
@@ -31,14 +33,23 @@ class TrustLevelManagerSpec extends Specification {
     def trustLevelPerDmiPlugin = [:]
 
     def mockInventoryPersistence = Mock(InventoryPersistence)
-    def mockAttributeValueChangeEventPublisher = Mock(AvcEventPublisher)
+    def mockAttributeValueChangeEventPublisher = Mock(CmAvcEventPublisher)
     def objectUnderTest = new TrustLevelManager(trustLevelPerCmHandle, trustLevelPerDmiPlugin, mockInventoryPersistence, mockAttributeValueChangeEventPublisher)
 
+    def 'Initial dmi registration'() {
+        given: 'a dmi plugin'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'dmi-1')
+        when: 'method to register to the cache is called'
+            objectUnderTest.registerDmiPlugin(dmiPluginRegistration)
+        then: 'dmi plugin in the cache and trusted'
+            assert trustLevelPerDmiPlugin.get('dmi-1') == TrustLevel.COMPLETE
+    }
+
     def 'Initial cm handle registration'() {
         given: 'two cm handles: one with no trust level and one trusted'
             def cmHandleModelsToBeCreated = ['ch-1': null, 'ch-2': TrustLevel.COMPLETE]
-        when: 'the initial registration handled'
-            objectUnderTest.handleInitialRegistrationOfTrustLevels(cmHandleModelsToBeCreated)
+        when: 'method to register to the cache is called'
+            objectUnderTest.registerCmHandles(cmHandleModelsToBeCreated)
         then: 'no notification sent'
             0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_)
         and: 'both cm handles are in the cache and are trusted'
@@ -49,8 +60,8 @@ class TrustLevelManagerSpec extends Specification {
     def 'Initial cm handle registration with a cm handle that is not trusted'() {
         given: 'a not trusted cm handle'
             def cmHandleModelsToBeCreated = ['ch-2': TrustLevel.NONE]
-        when: 'the initial registration handled'
-            objectUnderTest.handleInitialRegistrationOfTrustLevels(cmHandleModelsToBeCreated)
+        when: 'method to register to the cache is called'
+            objectUnderTest.registerCmHandles(cmHandleModelsToBeCreated)
         then: 'notification is sent'
             1 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_)
     }
@@ -61,7 +72,7 @@ class TrustLevelManagerSpec extends Specification {
         and: 'a trusted cm handle'
             trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
         when: 'the update is handled'
-            objectUnderTest.handleUpdateOfDmiTrustLevel('my-dmi', ['ch-1'], TrustLevel.NONE)
+            objectUnderTest.updateDmi('my-dmi', ['ch-1'], TrustLevel.NONE)
         then: 'notification is sent'
             1 * mockAttributeValueChangeEventPublisher.publishAvcEvent('ch-1', 'trustLevel', 'COMPLETE', 'NONE')
         and: 'the dmi in the cache is not trusted'
@@ -74,54 +85,74 @@ class TrustLevelManagerSpec extends Specification {
         and: 'a trusted cm handle'
             trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
         when: 'the update is handled'
-            objectUnderTest.handleUpdateOfDmiTrustLevel('my-dmi', ['ch-1'], TrustLevel.COMPLETE)
+            objectUnderTest.updateDmi('my-dmi', ['ch-1'], TrustLevel.COMPLETE)
         then: 'no notification is sent'
             0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_)
         and: 'the dmi in the cache is trusted'
             assert trustLevelPerDmiPlugin.get('my-dmi') == TrustLevel.COMPLETE
     }
 
-    def 'Device trust level updated'() {
+    def 'CmHandle trust level updated'() {
         given: 'a non trusted cm handle'
             trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE)
         and: 'a trusted dmi plugin'
             trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.COMPLETE)
         and: 'inventory persistence service returns yang model cm handle'
             mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(id: 'ch-1', dmiDataServiceName: 'my-dmi')
-        when: 'update of device to COMPLETE trust level handled'
-            objectUnderTest.handleUpdateOfDeviceTrustLevel('ch-1', TrustLevel.COMPLETE)
+        when: 'update of CmHandle to COMPLETE trust level handled'
+            objectUnderTest.updateCmHandleTrustLevel('ch-1', TrustLevel.COMPLETE)
         then: 'the cm handle in the cache is trusted'
             assert trustLevelPerCmHandle.get('ch-1', TrustLevel.COMPLETE)
         and: 'notification is sent'
             1 * mockAttributeValueChangeEventPublisher.publishAvcEvent('ch-1', 'trustLevel', 'NONE', 'COMPLETE')
     }
 
-    def 'Device trust level updated with same value'() {
+    def 'CmHandle trust level updated with same value'() {
         given: 'a non trusted cm handle'
             trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE)
         and: 'a trusted dmi plugin'
             trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.COMPLETE)
         and: 'inventory persistence service returns yang model cm handle'
             mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(id: 'ch-1', dmiDataServiceName: 'my-dmi')
-        when: 'update of device trust to the same level (NONE)'
-            objectUnderTest.handleUpdateOfDeviceTrustLevel('ch-1', TrustLevel.NONE)
+        when: 'update of CmHandle trust to the same level (NONE)'
+            objectUnderTest.updateCmHandleTrustLevel('ch-1', TrustLevel.NONE)
         then: 'the cm handle in the cache is not trusted'
             assert trustLevelPerCmHandle.get('ch-1', TrustLevel.NONE)
         and: 'no notification is sent'
             0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_)
     }
 
-    def 'Dmi trust level restored to complete with non trusted device'() {
+    def 'Dmi trust level restored to complete with non trusted CmHandle'() {
         given: 'a non trusted dmi'
             trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.NONE)
-        and: 'a non trusted device'
+        and: 'a non trusted CmHandle'
             trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE)
         when: 'restore the dmi trust level to COMPLETE'
-            objectUnderTest.handleUpdateOfDmiTrustLevel('my-dmi', ['ch-1'], TrustLevel.COMPLETE)
+            objectUnderTest.updateDmi('my-dmi', ['ch-1'], TrustLevel.COMPLETE)
         then: 'the cm handle in the cache is still NONE'
             assert trustLevelPerCmHandle.get('ch-1') == TrustLevel.NONE
         and: 'no notification is sent'
             0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_)
     }
 
+    def 'Select effective trust level among CmHandle and dmi plugin'() {
+        given: 'a non trusted dmi'
+            trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.NONE)
+        and: 'a trusted CmHandle'
+            trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
+        when: 'effective trust level selected'
+            def effectiveTrustLevel = objectUnderTest.getEffectiveTrustLevel('my-dmi', 'ch-1')
+        then: 'effective trust level is trusted'
+            assert effectiveTrustLevel == TrustLevel.NONE
+    }
+
+    def 'CmHandle trust level removed'() {
+        given: 'a cm handle'
+            trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
+        when: 'the remove is handled'
+            objectUnderTest.removeCmHandles(['ch-1'])
+        then: 'cm handle removed from the cache'
+            assert trustLevelPerCmHandle.get('ch-1') == null
+    }
+
 }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfigurationSpec.groovy
new file mode 100644 (file)
index 0000000..cf5e1a3
--- /dev/null
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.policyexecutor
+
+import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.web.reactive.function.client.WebClient
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [PolicyExecutorHttpClientConfig])
+@EnableConfigurationProperties
+class PolicyExecutorWebClientConfigurationSpec extends Specification {
+
+    def webClientBuilder = Mock(WebClient.Builder) {
+        defaultHeaders(_) >> it
+        clientConnector(_) >> it
+        codecs(_) >> it
+        build() >> Mock(WebClient)
+    }
+
+    def httpClientConfiguration = Spy(PolicyExecutorHttpClientConfig.class)
+
+    def objectUnderTest = new PolicyExecutorWebClientConfiguration(httpClientConfiguration)
+
+    def 'Web client policy executor.'() {
+        when: 'create a web client for policy executor'
+            def result = objectUnderTest.policyExecutorWebClient(webClientBuilder)
+        then: 'a web client is created successfully'
+            assert result != null
+            assert result instanceof WebClient
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy
new file mode 100644 (file)
index 0000000..a497b45
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.utils
+
+import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException
+import org.onap.cps.spi.model.DataNode
+import spock.lang.Specification
+
+class AlternateIdMatcherSpec extends Specification {
+
+    def mockInventoryPersistence = Mock(InventoryPersistence)
+    def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence)
+
+    def setup() {
+        given: 'cm handle in the registry with alternate id /a/b'
+            mockInventoryPersistence.getCmHandleDataNodeByAlternateId('/a/b') >> new DataNode()
+        and: 'no other cm handle'
+            mockInventoryPersistence.getCmHandleDataNodeByAlternateId(_) >> { throw new DataNodeNotFoundException('', '') }
+    }
+
+    def 'Finding longest alternate id matches.'() {
+        expect: 'querying for alternate id a matching result found'
+            assert objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') != null
+        where: 'the following parameters are used'
+            scenario                   | targetAlternateId
+            'exact match'              | '/a/b'
+            'parent match'             | '/a/b/c'
+            'grand parent match'       | '/a/b/c/d'
+            'trailing separator match' | '/a/b/'
+    }
+
+    def 'Attempt to find longest alternate id match without any matches.'() {
+        when: 'attempt to find alternateId'
+            objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/')
+        then: 'no alternate id match found exception thrown'
+            def thrown = thrown(NoAlternateIdMatchFoundException)
+        and: 'the exception has the relevant details from the error response'
+            assert thrown.message == 'No matching cm handle found using alternate ids'
+            assert thrown.details == 'cannot find a datanode with alternate id ' + targetAlternateId
+        where: 'the following parameters are used'
+            scenario                   | targetAlternateId
+            'no match for parent only' | '/a'
+            'no match for other child' | '/a/c'
+            'no match at all'          | '/x/y'
+    }
+
+    def 'Get cmHandle id from passed cmHandleReference (cmHandleId scenario)' () {
+        when: 'a cmHandleCmReference is passed in'
+            def result = objectUnderTest.getCmHandleId(cmHandleReference)
+        then: 'the inventory persistence service returns a cm handle (or not)'
+            mockInventoryPersistence.isExistingCmHandleId(cmHandleReference) >> existingCmHandleIdResponse
+            mockInventoryPersistence.getCmHandleDataNodeByAlternateId(cmHandleReference) >> alternateIdGetResponse
+        and: 'correct result is returned'
+            assert result == cmHandleReference
+        where:
+            cmHandleReference | existingCmHandleIdResponse | alternateIdGetResponse
+            'ch-1'            |  true                      |  ''
+            'alt-1'           |  false                     |  new DataNode(leaves: [id:'alt-1'])
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/EventDateTimeFormatterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/EventDateTimeFormatterSpec.groovy
new file mode 100644 (file)
index 0000000..7f07165
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.impl.utils
+
+import spock.lang.Specification
+
+import java.time.Year
+
+class EventDateTimeFormatterSpec extends Specification {
+
+    def 'Get ISO formatted date and time.' () {
+        expect: 'iso formatted date and time starts with current year'
+            assert EventDateTimeFormatter.getCurrentIsoFormattedDateTime().startsWith(String.valueOf(Year.now()))
+    }
+
+    def 'Convert date time from string to OffsetDateTime type.'() {
+        when: 'date time as a string is converted to OffsetDateTime type'
+            def result = EventDateTimeFormatter.toIsoOffsetDateTime('2024-05-28T18:28:02.869+0100')
+        then: 'the result convert back back to a string is the same as the original timestamp (except the format of timezone offset)'
+            assert result.toString() == '2024-05-28T18:28:02.869+01:00'
+    }
+
+    def 'Convert blank string.' () {
+        expect: 'converting a blank string result in null'
+            assert EventDateTimeFormatter.toIsoOffsetDateTime(' ') == null
+    }
+
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.utils
+package org.onap.cps.ncmp.impl.utils
 
 import org.onap.cps.spi.model.DataNode
 import spock.lang.Specification
@@ -33,8 +33,7 @@ class YangDataConverterSpec extends Specification{
                     leaves: ['name': 'pubProp1', 'value': 'pubValue1'])
             def dataNodeCmHandle = new DataNode(leaves:['id':'sample-id'], childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties])
         when: 'the dataNode is converted'
-            def yangModelCmHandle =
-                    YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle)
+            def yangModelCmHandle = YangDataConverter.toYangModelCmHandle(dataNodeCmHandle)
         then: 'the converted object has the correct id'
             assert yangModelCmHandle.id == 'sample-id'
         and: 'the additional (dmi, private) properties are included'
@@ -50,7 +49,7 @@ class YangDataConverterSpec extends Specification{
             def dataNodes = [new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'some-cm-handle\']', leaves: ['id':'some-cm-handle']),
                              new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'another-cm-handle\']', leaves: ['id':'another-cm-handle'])]
         when: 'the data nodes are converted'
-            def yangModelCmHandles = YangDataConverter.convertDataNodesToYangModelCmHandles(dataNodes)
+            def yangModelCmHandles = YangDataConverter.toYangModelCmHandles(dataNodes)
         then: 'verify both have returned and CmHandleIds are correct'
             assert yangModelCmHandles.size() == 2
             assert yangModelCmHandles.id.containsAll(['some-cm-handle', 'another-cm-handle'])
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilderSpec.groovy
new file mode 100644 (file)
index 0000000..9e05115
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022-2024 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.impl.utils.http
+
+import spock.lang.Specification
+
+class RestServiceUrlTemplateBuilderSpec extends Specification {
+
+    def objectUnderTest = new RestServiceUrlTemplateBuilder()
+
+    def 'Build URL template parameters with (variable) path segments and query parameters.'() {
+        given: 'the URL details are given to the builder'
+            objectUnderTest.fixedPathSegment('segment')
+            objectUnderTest.variablePathSegment('myVariableSegment','someValue')
+            objectUnderTest.fixedPathSegment('segment?with:special&characters')
+            objectUnderTest.queryParameter('param1', 'abc')
+            objectUnderTest.queryParameter('param2', 'value?with#special:characters')
+        when: 'the URL template parameters are created'
+            def result = objectUnderTest.createUrlTemplateParameters('myServer', 'myBasePath')
+        then: 'the URL template contains variable names instead of value and un-encoded fixed segment'
+            assert result.urlTemplate == 'myServer/myBasePath/v1/segment/{myVariableSegment}/segment?with:special&characters?param1={param1}&param2={param2}'
+        and: 'URL variables contains name and un-encoded value pairs'
+            assert result.urlVariables == ['myVariableSegment': 'someValue', 'param1': 'abc', 'param2': 'value?with#special:characters']
+    }
+
+    def 'Build URL template parameters with special characters in query parameters.'() {
+        given: 'the query parameter is given to the builder'
+           objectUnderTest.queryParameter('my&param', 'special&characters=are?not\\encoded')
+        when: 'the URL template parameters are created'
+            def result = objectUnderTest.createUrlTemplateParameters('myServer', 'myBasePath')
+        then: 'Special characters are not encoded'
+            assert result.urlVariables == ['my&param': 'special&characters=are?not\\encoded']
+    }
+
+    def 'Build URL template parameters with empty query parameters.'() {
+        when: 'the query parameter is given to the builder'
+            objectUnderTest.queryParameter('param', value)
+        and: 'the URL template parameters are create'
+            def result = objectUnderTest.createUrlTemplateParameters('myServer', 'myBasePath')
+        then: 'no parameter gets added'
+            assert result.urlVariables.isEmpty()
+        where: 'the following parameter values are used'
+            value << [ null, '', ' ' ]
+    }
+}
index b0be29d..38eeaa5 100644 (file)
@@ -23,16 +23,16 @@ package org.onap.cps.ncmp.init
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
 import ch.qos.logback.core.read.ListAppender
-import org.onap.cps.api.CpsDataspaceService
 import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsDataspaceService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException
+import org.onap.cps.ncmp.exceptions.NcmpStartUpException
 import org.onap.cps.spi.CascadeDeleteAllowed
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
-import org.springframework.boot.SpringApplication
 import org.slf4j.LoggerFactory
-import org.springframework.boot.context.event.ApplicationReadyEvent
+import org.springframework.boot.SpringApplication
+import org.springframework.boot.context.event.ApplicationStartedEvent
 import org.springframework.context.annotation.AnnotationConfigApplicationContext
 import spock.lang.Specification
 
@@ -64,18 +64,18 @@ class AbstractModelLoaderSpec extends Specification {
         applicationContext.close()
     }
 
-    def 'Application ready event'() {
-        when: 'Application (ready) event is triggered'
-            objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent))
+    def 'Application started event'() {
+        when: 'Application (started) event is triggered'
+            objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent))
         then: 'the onboard/upgrade method is executed'
             1 * objectUnderTest.onboardOrUpgradeModel()
     }
 
-    def 'Application ready event with start up exception'() {
+    def 'Application started event with start up exception'() {
         given: 'a start up exception is thrown doing model onboarding'
             objectUnderTest.onboardOrUpgradeModel() >> { throw new NcmpStartUpException('test message','details are not logged') }
-        when: 'Application (ready) event is triggered'
-            objectUnderTest.onApplicationEvent(new ApplicationReadyEvent(new SpringApplication(), null, applicationContext, null))
+        when: 'Application (started) event is triggered'
+            objectUnderTest.onApplicationEvent(new ApplicationStartedEvent(new SpringApplication(), null, applicationContext, null))
         then: 'the exception message is logged'
             def logs = loggingListAppender.list.toString()
             assert logs.contains('test message')
index f3b405b..caaad8d 100644 (file)
@@ -27,15 +27,15 @@ import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsDataspaceService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException
+import org.onap.cps.ncmp.exceptions.NcmpStartUpException
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.model.Dataspace
 import org.slf4j.LoggerFactory
-import org.springframework.boot.context.event.ApplicationReadyEvent
+import org.springframework.boot.context.event.ApplicationStartedEvent
 import org.springframework.context.annotation.AnnotationConfigApplicationContext
 import spock.lang.Specification
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
 
 class CmDataSubscriptionModelLoaderSpec extends Specification {
 
@@ -65,11 +65,11 @@ class CmDataSubscriptionModelLoaderSpec extends Specification {
         applicationContext.close()
     }
 
-    def 'Onboard subscription model via application ready event.'() {
+    def 'Onboard subscription model via application started event.'() {
         given: 'dataspace is ready for use'
             mockCpsDataspaceService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('')
         when: 'the application is ready'
-            objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent))
+            objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent))
         then: 'the module service to create schema set is called once'
             1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', expectedYangResourcesToContentMap)
         and: 'the admin service to create an anchor set is called once'
index cd659bb..76c1589 100644 (file)
 
 package org.onap.cps.ncmp.init
 
-import org.onap.cps.api.CpsAnchorService
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
 import ch.qos.logback.core.read.ListAppender
-import org.onap.cps.api.CpsDataspaceService
+import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsDataspaceService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.spi.model.Dataspace
 import org.slf4j.LoggerFactory
-import org.springframework.boot.context.event.ApplicationReadyEvent
+import org.springframework.boot.context.event.ApplicationStartedEvent
 import org.springframework.context.annotation.AnnotationConfigApplicationContext
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+
 class InventoryModelLoaderSpec extends Specification {
 
     def mockCpsAdminService = Mock(CpsDataspaceService)
@@ -68,8 +67,8 @@ class InventoryModelLoaderSpec extends Specification {
     def 'Onboard subscription model via application ready event.'() {
         given: 'dataspace is ready for use'
             mockCpsAdminService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('')
-        when: 'the application is ready'
-            objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent))
+        when: 'the application is started'
+            objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent))
         then: 'the module service is used to create the new schema set from the correct resource'
             1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'dmi-registry-2024-02-23', expectedYangResourceToContentMap)
         and: 'the admin service is used to update the anchor'
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient
+package org.onap.cps.ncmp.utils.events
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.CloudEvent
 import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext
-import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
+import org.onap.cps.ncmp.config.CpsApplicationContext
 import org.onap.cps.ncmp.events.avc.ncmp_to_client.Avc
 import org.onap.cps.ncmp.events.avc.ncmp_to_client.AvcEvent
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.test.context.ContextConfiguration
 
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
-
 @ContextConfiguration(classes = [CpsApplicationContext, ObjectMapper, JsonObjectMapper])
-class AvcEventPublisherSpec extends MessagingBaseSpec {
+class CmAvcEventPublisherSpec extends MessagingBaseSpec {
 
     def mockEventsPublisher = Mock(EventsPublisher<CloudEvent>)
-    def objectUnderTest = new AvcEventPublisher(mockEventsPublisher)
+    def objectUnderTest = new CmAvcEventPublisher(mockEventsPublisher)
 
     def 'Publish an attribute value change event'() {
         given: 'the event key'
@@ -52,7 +49,7 @@ class AvcEventPublisherSpec extends MessagingBaseSpec {
         then: 'the cloud event publisher is invoked with the correct data'
             1 * mockEventsPublisher.publishCloudEvent(_, someEventKey,
                 cloudEvent -> {
-                    def actualAvcs = toTargetEvent(cloudEvent, AvcEvent.class).data.attributeValueChange
+                    def actualAvcs = CloudEventMapper.toTargetEvent(cloudEvent, AvcEvent.class).data.attributeValueChange
                     def expectedAvc = new Avc(attributeName: someAttributeName,
                         oldAttributeValue: someOldAttributeValue,
                         newAttributeValue: someNewAttributeValue)
@@ -18,7 +18,8 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.kafka
+package org.onap.cps.ncmp.utils.events
+
 
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.kafka
+package org.onap.cps.ncmp.utils.events
 
 import io.cloudevents.CloudEvent
 import io.cloudevents.kafka.CloudEventSerializer
@@ -18,9 +18,9 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.rest.util
+package org.onap.cps.ncmp.utils.events
 
-import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException
+import org.onap.cps.ncmp.api.exceptions.InvalidTopicException
 import spock.lang.Specification
 
 class TopicValidatorSpec extends Specification {
diff --git a/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java b/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java
new file mode 100644 (file)
index 0000000..2f6b270
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.utils;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@TestConfiguration
+public class WebClientBuilderTestConfig {
+
+    /**
+     * Configures and creates a web client builder bean to make it accessible for the Spring Boot Test Context.
+     *
+     * @return a WebClient Builder instance.
+     */
+    @Bean
+    public WebClient.Builder webClientBuilder() {
+        return WebClient.builder();
+    }
+
+}
index 574b499..c76831d 100644 (file)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2023 Nordix Foundation
+#  Copyright (C) 2021-2024 Nordix Foundation
 #  ================================================================================
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
 #  SPDX-License-Identifier: Apache-2.0
 #  ============LICENSE_END=========================================================
 
+cps:
+    tracing:
+        sampler:
+            jaeger_remote:
+                endpoint: http://jaeger-Remote-test-url
+        exporter:
+            endpoint: http://exporter-test-url
+        enabled: true
+
 spring:
     kafka:
         producer:
@@ -30,14 +39,34 @@ app:
         async-m2m:
             topic: ncmp-async-m2m
         avc:
-            subscription-topic: subscription
+            cm-subscription-ncmp-in: subscription
             cm-events-topic: cm-events
-            subscription-forward-topic-prefix: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-}
+            cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription}
 
 ncmp:
     dmi:
         httpclient:
-            connectionTimeoutInSeconds: 180
+            data-services:
+                maximumInMemorySizeInMegabytes: 1
+                maximumConnectionsTotal: 2
+                pendingAcquireMaxCount: 3
+                connectionTimeoutInSeconds: 4
+                readTimeoutInSeconds: 5
+                writeTimeoutInSeconds: 6
+            model-services:
+                maximumInMemorySizeInMegabytes: 11
+                maximumConnectionsTotal: 12
+                pendingAcquireMaxCount: 13
+                connectionTimeoutInSeconds: 14
+                readTimeoutInSeconds: 15
+                writeTimeoutInSeconds: 16
+            healthCheckServices:
+                maximumInMemorySizeInMegabytes: 21
+                maximumConnectionsTotal: 22
+                pendingAcquireMaxCount: 23
+                connectionTimeoutInSeconds: 24
+                readTimeoutInSeconds: 25
+                writeTimeoutInSeconds: 26
         auth:
             username: some-user
             password: some-password
@@ -52,6 +81,19 @@ ncmp:
         async-executor:
             parallelism-level: 3
 
+    policy-executor:
+        enabled: true
+        server:
+            address: http://localhost
+            port: 8785
+        httpclient:
+            all-services:
+                maximumInMemorySizeInMegabytes: 31
+                maximumConnectionsTotal: 32
+                pendingAcquireMaxCount: 33
+                connectionTimeoutInSeconds: 34
+                readTimeoutInSeconds: 35
+                writeTimeoutInSeconds: 36
 
 # Custom Hazelcast Config.
 hazelcast:
index 6b66549..04d37b8 100644 (file)
@@ -6,14 +6,14 @@
         "targetFilter":  ["ch1","ch2"],
         "scopeFilter": {
           "datastore": "ncmp-datastore:passthrough-operational",
-          "xpath-filter": ["/x1/y1","x2/y2"]
+          "xpathFilter": ["/x1/y1","x2/y2"]
         }
       },
       {
         "targetFilter":  ["ch3","ch4"],
         "scopeFilter": {
           "datastore": "ncmp-datastore:passthrough-operational",
-          "xpath-filter": ["/x3/y3","x4/y4"]
+          "xpathFilter": ["/x3/y3","x4/y4"]
         }
       }
     ]
index 5b297c8..1dc14bd 100644 (file)
               "operation":"replace",
               "target":"ran-network:ran-network/NearRTRIC[@id='22']/GNBCUCPFunction[@id='cucpserver2']/NRCellCU[@id='15549']/NRCellRelation[@id='14427']",
               "value":{
-                "attributes":[
-                  {
-                    "isHoAllowed":true
-                  }
-                ]
+                "color": "yellow",
+                "name": "Apple"
               }
             },
             {
index 594aa1f..430f4b5 100644 (file)
@@ -32,7 +32,7 @@
 
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-parent</artifactId>
-    <version>3.5.0-SNAPSHOT</version>
+    <version>3.5.3-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <properties>
                         <exclude>org/onap/cps/ncmp/rest/model/*</exclude>
                         <exclude>org/onap/cps/**/*MapperImpl.class</exclude>
                         <exclude>org/onap/cps/ncmp/rest/stub/*</exclude>
+                        <exclude>org/onap/cps/policyexecutor/stub/model/*</exclude>
                     </excludes>
                 </configuration>
                 <executions>
                 <artifactId>sonar-maven-plugin</artifactId>
                 <version>3.9.1.2184</version>
             </plugin>
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>exec-maven-plugin</artifactId>
-                <version>1.6.0</version>
-                <executions>
-                    <execution>
-                        <id>generate-csv</id>
-                        <phase>prepare-package</phase>
-                        <goals>
-                            <goal>exec</goal>
-                        </goals>
-                    </execution>
-                </executions>
-                <configuration>
-                    <executable>${script.executor}</executable>
-                    <workingDirectory>${parent.directory}/cps-ri/src/main/resources/</workingDirectory>
-                    <arguments>
-                        <argument>yangResourceCsvGenerator.py</argument>
-                        <argument>dmi-registry@2021-12-13</argument>
-                        <argument>dmi-registry@2022-02-10</argument>
-                        <argument>dmi-registry@2022-05-10</argument>
-                    </arguments>
-                </configuration>
-            </plugin>
         </plugins>
     </build>
 </project>
index 3176d9a..f71d9aa 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 3aef120..444702d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 
 grammar CpsPath ;
 
-cpsPath : ( prefix | descendant | incorrectPrefix ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? invalidPostFix?;
+cpsPath : ( prefix | descendant ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? EOF ;
 
-ancestorAxis : SLASH KW_ANCESTOR COLONCOLON ancestorPath ;
+slash : SLASH ;
 
-ancestorPath : yangElement ( SLASH yangElement)* ;
+ancestorAxis : KW_ANCESTOR_AXIS_PREFIX ancestorPath ;
 
-textFunctionCondition : SLASH leafName OB KW_TEXT_FUNCTION EQ StringLiteral CB ;
+ancestorPath : yangElement ( slash yangElement)* ;
 
-containsFunctionCondition : OB KW_CONTAINS_FUNCTION OP AT leafName COMMA StringLiteral CP CB ;
+textFunctionCondition : slash leafName OB KW_TEXT_FUNCTION EQ StringLiteral CB ;
 
-parent : ( SLASH yangElement)* ;
+containsFunctionCondition : OB KW_CONTAINS_FUNCTION OP AT leafName COMMA StringLiteral CP CB ;
 
-prefix : parent SLASH containerName ;
+parent : ( slash yangElement)* ;
 
-descendant : SLASH prefix ;
+prefix : parent slash containerName ;
 
-incorrectPrefix : SLASH SLASH SLASH+ ;
+descendant : slash prefix ;
 
 yangElement : containerName listElementRef? ;
 
@@ -61,8 +61,6 @@ booleanOperators : ( KW_AND | KW_OR ) ;
 
 comparativeOperators : ( EQ | GT | LT | GE | LE ) ;
 
-invalidPostFix : (AT | CB | COLONCOLON | comparativeOperators ).+ ;
-
 /*
  * Lexer Rules
  * Most of the lexer rules below are inspired by
@@ -89,7 +87,8 @@ KW_ANCESTOR : 'ancestor' ;
 KW_AND : 'and' ;
 KW_TEXT_FUNCTION: 'text()' ;
 KW_OR : 'or' ;
-KW_CONTAINS_FUNCTION: 'contains' ;
+KW_CONTAINS_FUNCTION : 'contains' ;
+KW_ANCESTOR_AXIS_PREFIX : SLASH KW_ANCESTOR COLONCOLON ;
 
 IntegerLiteral : FragDigits ;
 // Add below type definitions for leafvalue comparision in https://jira.onap.org/browse/CPS-440
index de261e6..ed7dbec 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,7 +29,6 @@ import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.AncestorAxisContext;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.DescendantContext;
-import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.IncorrectPrefixContext;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.LeafConditionContext;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.MultipleLeafConditionsContext;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.PrefixContext;
@@ -37,17 +36,19 @@ import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.TextFunctionConditionCon
 
 public class CpsPathBuilder extends CpsPathBaseListener {
 
+    private static final String NO_PARENT_PATH = "";
+
     private static final String OPEN_BRACKET = "[";
 
     private static final String CLOSE_BRACKET = "]";
 
     private final CpsPathQuery cpsPathQuery = new CpsPathQuery();
 
-    private final List<CpsPathQuery.DataLeaf> leavesData = new ArrayList<>();
+    private final List<CpsPathQuery.LeafCondition> leafConditions = new ArrayList<>();
 
     private final StringBuilder normalizedXpathBuilder = new StringBuilder();
 
-    private final StringBuilder normalizedAncestorPathBuilder = new StringBuilder();
+    private int startIndexOfAncestorSchemaNodeIdentifier = 0;
 
     private boolean processingAncestorAxis = false;
 
@@ -55,11 +56,9 @@ public class CpsPathBuilder extends CpsPathBaseListener {
 
     private final List<String> booleanOperators = new ArrayList<>();
 
-    private final List<String> comparativeOperators = new ArrayList<>();
-
     @Override
-    public void exitInvalidPostFix(final CpsPathParser.InvalidPostFixContext ctx) {
-        throw new PathParsingException(ctx.getText());
+    public void exitSlash(final CpsPathParser.SlashContext ctx) {
+        normalizedXpathBuilder.append("/");
     }
 
     @Override
@@ -69,16 +68,19 @@ public class CpsPathBuilder extends CpsPathBaseListener {
 
     @Override
     public void exitParent(final CpsPathParser.ParentContext ctx) {
-        cpsPathQuery.setNormalizedParentPath(normalizedXpathBuilder.toString());
-    }
-
-    @Override
-    public void exitIncorrectPrefix(final IncorrectPrefixContext ctx) {
-        throw new PathParsingException("CPS path can only start with one or two slashes (/)");
+        final String normalizedParentPath;
+        if (normalizedXpathBuilder.toString().equals("/")) {
+            normalizedParentPath = NO_PARENT_PATH;
+        } else {
+            normalizedParentPath = normalizedXpathBuilder.toString();
+        }
+        cpsPathQuery.setNormalizedParentPath(normalizedParentPath);
     }
 
     @Override
     public void exitLeafCondition(final LeafConditionContext ctx) {
+        final String leafName = ctx.leafName().getText();
+        final String operator = ctx.comparativeOperators().getText();
         final Object comparisonValue;
         if (ctx.IntegerLiteral() != null) {
             comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText());
@@ -87,7 +89,7 @@ public class CpsPathBuilder extends CpsPathBaseListener {
         } else {
             throw new PathParsingException("Unsupported comparison value encountered in expression" + ctx.getText());
         }
-        leafContext(ctx.leafName(), comparisonValue);
+        leafContext(leafName, operator, comparisonValue);
     }
 
     @Override
@@ -95,41 +97,37 @@ public class CpsPathBuilder extends CpsPathBaseListener {
         booleanOperators.add(ctx.getText());
     }
 
-    @Override
-    public void exitComparativeOperators(final CpsPathParser.ComparativeOperatorsContext ctx) {
-        comparativeOperators.add(ctx.getText());
-    }
-
     @Override
     public void exitDescendant(final DescendantContext ctx) {
         cpsPathQuery.setCpsPathPrefixType(DESCENDANT);
-        cpsPathQuery.setDescendantName(normalizedXpathBuilder.substring(1));
-        normalizedXpathBuilder.insert(0, "/");
+        cpsPathQuery.setDescendantName(normalizedXpathBuilder.substring(2));
     }
 
     @Override
     public void enterMultipleLeafConditions(final MultipleLeafConditionsContext ctx)  {
         normalizedXpathBuilder.append(OPEN_BRACKET);
-        leavesData.clear();
+        leafConditions.clear();
         booleanOperators.clear();
-        comparativeOperators.clear();
     }
 
     @Override
     public void exitMultipleLeafConditions(final MultipleLeafConditionsContext ctx) {
         normalizedXpathBuilder.append(CLOSE_BRACKET);
-        cpsPathQuery.setLeavesData(leavesData);
+        cpsPathQuery.setLeafConditions(leafConditions);
     }
 
     @Override
     public void enterAncestorAxis(final AncestorAxisContext ctx) {
         processingAncestorAxis = true;
+        normalizedXpathBuilder.append("/ancestor::");
+        startIndexOfAncestorSchemaNodeIdentifier = normalizedXpathBuilder.length();
     }
 
     @Override
     public void exitAncestorAxis(final AncestorAxisContext ctx) {
-        cpsPathQuery.setAncestorSchemaNodeIdentifier(normalizedAncestorPathBuilder.substring(1));
         processingAncestorAxis = false;
+        cpsPathQuery.setAncestorSchemaNodeIdentifier(
+                normalizedXpathBuilder.substring(startIndexOfAncestorSchemaNodeIdentifier));
     }
 
     @Override
@@ -147,48 +145,36 @@ public class CpsPathBuilder extends CpsPathBaseListener {
     @Override
     public void enterListElementRef(final CpsPathParser.ListElementRefContext ctx) {
         normalizedXpathBuilder.append(OPEN_BRACKET);
-        if (processingAncestorAxis) {
-            normalizedAncestorPathBuilder.append(OPEN_BRACKET);
-        }
     }
 
     @Override
     public void exitListElementRef(final CpsPathParser.ListElementRefContext ctx) {
         normalizedXpathBuilder.append(CLOSE_BRACKET);
-        if (processingAncestorAxis) {
-            normalizedAncestorPathBuilder.append(CLOSE_BRACKET);
-        }
     }
 
     CpsPathQuery build() {
         cpsPathQuery.setNormalizedXpath(normalizedXpathBuilder.toString());
         cpsPathQuery.setContainerNames(containerNames);
         cpsPathQuery.setBooleanOperators(booleanOperators);
-        cpsPathQuery.setComparativeOperators(comparativeOperators);
         return cpsPathQuery;
     }
 
     @Override
     public void exitContainerName(final CpsPathParser.ContainerNameContext ctx) {
         final String containerName = ctx.getText();
-        normalizedXpathBuilder.append("/")
-                .append(containerName);
-        containerNames.add(containerName);
-        if (processingAncestorAxis) {
-            normalizedAncestorPathBuilder.append("/").append(containerName);
+        normalizedXpathBuilder.append(containerName);
+        if (!processingAncestorAxis) {
+            containerNames.add(containerName);
         }
     }
 
-    private void leafContext(final CpsPathParser.LeafNameContext ctx, final Object comparisonValue) {
-        leavesData.add(new CpsPathQuery.DataLeaf(ctx.getText(), comparisonValue));
-        appendCondition(normalizedXpathBuilder, ctx.getText(), comparisonValue);
-        if (processingAncestorAxis) {
-            appendCondition(normalizedAncestorPathBuilder, ctx.getText(), comparisonValue);
-        }
+    private void leafContext(final String leafName, final String operator, final Object comparisonValue) {
+        leafConditions.add(new CpsPathQuery.LeafCondition(leafName, operator, comparisonValue));
+        appendCondition(normalizedXpathBuilder, leafName, operator, comparisonValue);
     }
 
     private void appendCondition(final StringBuilder currentNormalizedPathBuilder, final String name,
-                                 final Object value) {
+                                 final String operator, final Object value) {
         final char lastCharacter = currentNormalizedPathBuilder.charAt(currentNormalizedPathBuilder.length() - 1);
         final boolean isStartOfExpression = lastCharacter == '[';
         if (!isStartOfExpression) {
@@ -196,7 +182,7 @@ public class CpsPathBuilder extends CpsPathBaseListener {
         }
         currentNormalizedPathBuilder.append("@")
                                     .append(name)
-                                    .append(getLastElement(comparativeOperators))
+                                    .append(operator)
                                     .append("'")
                                     .append(value.toString().replace("'", "''"))
                                     .append("'");
index f98df05..03602b6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,8 +25,6 @@ import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE;
 
 import java.util.List;
 import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
-import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.Setter;
 
@@ -40,12 +38,11 @@ public class CpsPathQuery {
     private List<String> containerNames;
     private CpsPathPrefixType cpsPathPrefixType = ABSOLUTE;
     private String descendantName;
-    private List<DataLeaf> leavesData;
+    private List<LeafCondition> leafConditions;
     private String ancestorSchemaNodeIdentifier = "";
     private String textFunctionConditionLeafName;
     private String textFunctionConditionValue;
     private List<String> booleanOperators;
-    private List<String> comparativeOperators;
     private String containsFunctionConditionLeafName;
     private String containsFunctionConditionValue;
 
@@ -74,7 +71,7 @@ public class CpsPathQuery {
      * @return boolean value.
      */
     public boolean hasLeafConditions() {
-        return leavesData != null;
+        return leafConditions != null;
     }
 
     /**
@@ -104,11 +101,6 @@ public class CpsPathQuery {
         return cpsPathPrefixType == ABSOLUTE && hasLeafConditions();
     }
 
-    @Getter
-    @EqualsAndHashCode
-    @AllArgsConstructor
-    public static class DataLeaf {
-        private final String name;
-        private final Object value;
-    }
+    public record LeafCondition(String name, String operator, Object value) { }
+
 }
index ae7ee59..16430d2 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -48,8 +48,8 @@ class CpsPathQuerySpec extends Specification {
         and: 'the right query parameters are set'
             assert result.xpathPrefix == expectedXpathPrefix
             assert result.hasLeafConditions()
-            assert result.leavesData[0].name == expectedLeafName
-            assert result.leavesData[0].value == expectedLeafValue
+            assert result.leafConditions[0].name() == expectedLeafName
+            assert result.leafConditions[0].value() == expectedLeafValue
         where: 'the following data is used'
             scenario               | cpsPath                                                    || expectedXpathPrefix                             | expectedLeafName       | expectedLeafValue
             'leaf of type String'  | '/parent/child[@common-leaf-name="common-leaf-value"]'     || '/parent/child'                                 | 'common-leaf-name'     | 'common-leaf-value'
@@ -91,13 +91,14 @@ class CpsPathQuerySpec extends Specification {
             'descendant with leaf condition has "<" operator'             | '//cps-path[@key<10]'                                          || "//cps-path[@key<'10']"
             'descendant with leaf condition has ">=" operator'            | '//cps-path[@key>=8]'                                          || "//cps-path[@key>='8']"
             'descendant with leaf condition has "<=" operator'            | '//cps-path[@key<=12]'                                         || "//cps-path[@key<='12']"
-            'descendant with leaf value and ancestor'                     | '//cps-path[@key=1]/ancestor:parent[@key=1]'                   || "//cps-path[@key='1']/ancestor:parent[@key='1']"
+            'descendant with leaf value and ancestor'                     | '//cps-path[@key=1]/ancestor::parent[@key=1]'                  || "//cps-path[@key='1']/ancestor::parent[@key='1']"
             'parent & child'                                              | '/parent/child'                                                || '/parent/child'
             'parent leaf of type Integer & child'                         | '/parent/child[@code=1]/child2'                                || "/parent/child[@code='1']/child2"
             'parent leaf with double quotes'                              | '/parent/child[@code="1"]/child2'                              || "/parent/child[@code='1']/child2"
             'parent leaf with double quotes inside single quotes'         | '/parent/child[@code=\'"1"\']/child2'                          || "/parent/child[@code='\"1\"']/child2"
             'parent leaf with single quotes inside double quotes'         | '/parent/child[@code="\'1\'"]/child2'                          || "/parent/child[@code='''1''']/child2"
             'leaf with single quotes inside double quotes'                | '/parent/child[@code="\'1\'"]'                                 || "/parent/child[@code='''1''']"
+            'leaf with single quotes inside single quotes'                | "/parent/child[@code='I''m quoted']"                           || "/parent/child[@code='I''m quoted']"
             'leaf with more than one attribute'                           | '/parent/child[@key1=1 and @key2="abc"]'                       || "/parent/child[@key1='1' and @key2='abc']"
             'parent & child with more than one attribute'                 | '/parent/child[@key1=1 and @key2="abc"]/child2'                || "/parent/child[@key1='1' and @key2='abc']/child2"
             'leaf with more than one attribute has OR operator'           | '/parent/child[@key1=1 or @key2="abc"]'                        || "/parent/child[@key1='1' or @key2='abc']"
@@ -123,11 +124,11 @@ class CpsPathQuerySpec extends Specification {
         when: 'the given cps path is parsed'
             def result = CpsPathQuery.createFrom(cpsPath)
         then: 'the expected number of leaves are returned'
-            result.leavesData.size() == expectedNumberOfLeaves
+            result.leafConditions.size() == expectedNumberOfLeaves
         and: 'the given operator(s) returns in the correct order'
             result.booleanOperators == expectedOperators
         and: 'the given comparativeOperator(s) returns in the correct order'
-            result.comparativeOperators == expectedComparativeOperator
+            result.leafConditions.operator == expectedComparativeOperator
         where: 'the following data is used'
             cpsPath                                                                                   || expectedNumberOfLeaves || expectedOperators || expectedComparativeOperator
             '/parent[@code=1]/child[@common-leaf-name-int=5]'                                         || 1                      || []                || ['=']
@@ -234,19 +235,19 @@ class CpsPathQuerySpec extends Specification {
         when: 'the given cps path is parsed using multiple conditions on same leaf'
             def result = CpsPathQuery.createFrom('/test[@same-name="value1" or @same-name="value2"]')
         then: 'two leaves are present with correct values'
-            assert result.leavesData.size() == 2
-            assert result.leavesData[0].name == "same-name"
-            assert result.leavesData[0].value == "value1"
-            assert result.leavesData[1].name == "same-name"
-            assert result.leavesData[1].value == "value2"
+            assert result.leafConditions.size() == 2
+            assert result.leafConditions[0].name == "same-name"
+            assert result.leafConditions[0].value == "value1"
+            assert result.leafConditions[1].name == "same-name"
+            assert result.leafConditions[1].value == "value2"
     }
 
     def 'Ordering of data leaves is preserved.'() {
         when: 'the given cps path is parsed'
             def result = CpsPathQuery.createFrom(cpsPath)
         then: 'the order of the data leaves is preserved'
-            assert result.leavesData[0].name == expectedFirstLeafName
-            assert result.leavesData[1].name == expectedSecondLeafName
+            assert result.leafConditions[0].name == expectedFirstLeafName
+            assert result.leafConditions[1].name == expectedSecondLeafName
         where: 'the following data is used'
             cpsPath                                      || expectedFirstLeafName | expectedSecondLeafName
             '/test[@name1="value1" and @name2="value2"]' || 'name1'               | 'name2'
index d62f337..29bb3c7 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -36,21 +36,40 @@ class CpsPathUtilSpec extends Specification {
             'single quotes' | "/parent/child[@common-leaf-name='123']"
     }
 
-    def 'Normalized parent xpaths'() {
-        when: 'a given xpath with #scenario is parsed'
-            def result = CpsPathUtil.getNormalizedParentXpath(xpath)
+    def 'Normalized parent paths of absolute paths'() {
+        when: 'a given cps path is parsed'
+            def result = CpsPathUtil.getNormalizedParentXpath(cpsPath)
         then: 'the result is the expected parent path'
             assert result == expectedParentPath
-        where: 'the following xpaths are used'
-            scenario                         | xpath                                 || expectedParentPath
-            'no child'                       | '/parent'                             || ''
-            'child and parent'               | '/parent/child'                       || '/parent'
-            'grand child'                    | '/parent/child/grandChild'            || '/parent/child'
-            'parent & top is list element'   | '/parent[@id=1]/child'                || "/parent[@id='1']"
-            'parent is list element'         | '/parent/child[@id=1]/grandChild'     || "/parent/child[@id='1']"
-            'parent is list element with /'  | "/parent/child[@id='a/b']/grandChild" || "/parent/child[@id='a/b']"
-            'parent is list element with ['  | "/parent/child[@id='a[b']/grandChild" || "/parent/child[@id='a[b']"
-            'parent is list element using "' | '/parent/child[@id="x"]/grandChild'   || "/parent/child[@id='x']"
+        where: 'the following absolute cps paths are used'
+            cpsPath                               || expectedParentPath
+            '/parent'                             || ''
+            '/parent/child'                       || '/parent'
+            '/parent/child/grandChild'            || '/parent/child'
+            '/parent[@id=1]/child'                || "/parent[@id='1']"
+            '/parent/child[@id=1]/grandChild'     || "/parent/child[@id='1']"
+            '/parent/child/grandChild[@id="x"]'   || "/parent/child"
+            '/parent/ancestor::grandparent'       || ''
+            '/parent/child/ancestor::grandparent' || '/parent'
+            '/parent/child/name[text()="value"]'  || '/parent'
+    }
+
+    def 'Normalized parent paths of descendant paths'() {
+        when: 'a given cps path is parsed'
+            def result = CpsPathUtil.getNormalizedParentXpath(cpsPath)
+        then: 'the result is the expected parent path'
+            assert result == expectedParentPath
+        where: 'the following descendant cps paths are used'
+            cpsPath                                || expectedParentPath
+            '//parent'                             || ''
+            '//parent/child'                       || '//parent'
+            '//parent/child/grandChild'            || '//parent/child'
+            '//parent[@id=1]/child'                || "//parent[@id='1']"
+            '//parent/child[@id=1]/grandChild'     || "//parent/child[@id='1']"
+            '//parent/child/grandChild[@id="x"]'   || "//parent/child"
+            '//parent/ancestor::grandparent'       || ''
+            '//parent/child/ancestor::grandparent' || '//parent'
+            '//parent/child/name[text()="value"]'  || '//parent'
     }
 
     def 'Get node ID sequence for given xpath'() {
@@ -67,17 +86,19 @@ class CpsPathUtilSpec extends Specification {
             'parent is list element'         | '/parent/child[@id=1]/grandChild'     || ["parent","child","grandChild"]
             'parent is list element with /'  | "/parent/child[@id='a/b']/grandChild" || ["parent","child","grandChild"]
             'parent is list element with ['  | "/parent/child[@id='a[b']/grandChild" || ["parent","child","grandChild"]
+            'does not include ancestor node' | '/parent/child/ancestor::grandparent' || ["parent","child"]
     }
 
     def 'Recognizing (absolute) xpaths to List elements'() {
         expect: 'check for list returns the correct values'
             assert CpsPathUtil.isPathToListElement(xpath) == expectList
         where: 'the following xpaths are used'
-            xpath                  || expectList
-            '/parent[@id=1]'       || true
-            '/parent[@id=1]/child' || false
-            '/parent/child[@id=1]' || true
-            '//child[@id=1]'       || false
+            xpath                                  || expectList
+            '/parent[@id=1]'                       || true
+            '/parent[@id=1]/child'                 || false
+            '/parent/child[@id=1]'                 || true
+            '//child[@id=1]'                       || false
+            '/parent/ancestor::grandparent[@id=1]' || false
     }
 
     def 'Parsing Exception'() {
index c1b111b..25ef6a4 100644 (file)
@@ -1,7 +1,7 @@
 # ============LICENSE_START=======================================================
 # Copyright (c) 2021-2022 Bell Canada.
 # Modifications Copyright (C) 2021-2023 Nordix Foundation
-# Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
+# Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
 # Modifications Copyright (C) 2022 Deutsche Telekom AG
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -114,6 +114,8 @@ components:
               <categories>
                 <code>1</code>
                 <name>SciFi</name>
+                <code>2</code>
+                <name>kids</name>
               </categories>
             </bookstore>
           </stores>
@@ -139,17 +141,17 @@ components:
                   name: kids
     deltaReportSample:
       value:
-        - action: "ADD"
+        - action: "create"
           xpath: "/bookstore/categories/[@code=3]"
           target-data:
             code: 3,
             name: "kidz"
-        - action: "REMOVE"
+        - action: "remove"
           xpath: "/bookstore/categories/[@code=1]"
           source-data:
             code: 1,
             name: "Fiction"
-        - action: "UPDATE"
+        - action: "replace"
           xpath: "/bookstore/categories/[@code=2]"
           source-data:
             name: "Funny"
@@ -181,6 +183,14 @@ components:
       schema:
         type: string
         example: my-anchor
+    sourceAnchorNameInPath:
+      name: source-anchor-name
+      in: path
+      description: source-anchor-name
+      required: true
+      schema:
+        type: string
+        example: my-anchor
     schemaSetNameInQuery:
       name: schema-set-name
       in: query
@@ -277,10 +287,10 @@ components:
         type: string
         enum: [v1, v2]
         default: v2
-    contentTypeHeader:
+    contentTypeInHeader:
       name: Content-Type
       in: header
-      description: Content type header
+      description: Content type in header
       schema:
         type: string
         example: 'application/json'
@@ -374,7 +384,7 @@ components:
     Created:
       description: Created
       content:
-        text/plain:
+        application/json:
           schema:
             type: string
             example: my-resource
index b9d2e59..4418a3b 100644 (file)
@@ -1,7 +1,7 @@
 # ============LICENSE_START=======================================================
 # Copyright (c) 2021-2022 Bell Canada.
 # Modifications Copyright (C) 2021-2022 Nordix Foundation
-# Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
+# Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
 # Modifications Copyright (C) 2022 Deutsche Telekom AG
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,15 +32,24 @@ listElementByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/requiredXpathInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
+      - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
       required: true
       content:
         application/json:
           schema:
-            type: object
+            type: string
           examples:
             dataSample:
               $ref: 'components.yml#/components/examples/dataSample'
+        application/xml:
+          schema:
+            type: object
+            xml:
+              name: stores
+          examples:
+            dataSample:
+              $ref: 'components.yml#/components/examples/dataSampleXml'
     responses:
       '201':
         $ref: 'components.yml#/components/responses/Created'
@@ -94,7 +103,7 @@ nodesByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/xpathInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
-      - $ref: 'components.yml#/components/parameters/contentTypeHeader'
+      - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
       required: true
       content:
@@ -112,7 +121,6 @@ nodesByDataspaceAndAnchor:
           examples:
             dataSample:
               $ref: 'components.yml#/components/examples/dataSampleXml'
-
     responses:
       '201':
         $ref: 'components.yml#/components/responses/Created'
@@ -137,15 +145,24 @@ nodesByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/xpathInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
+      - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
       required: true
       content:
         application/json:
           schema:
-            type: object
+            type: string
           examples:
             dataSample:
               $ref: 'components.yml#/components/examples/dataSample'
+        application/xml:
+          schema:
+            type: object
+            xml:
+              name: stores
+          examples:
+            dataSample:
+              $ref: 'components.yml#/components/examples/dataSampleXml'
     responses:
       '200':
         $ref: 'components.yml#/components/responses/Ok'
@@ -188,15 +205,24 @@ nodesByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/xpathInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
+      - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
       required: true
       content:
         application/json:
           schema:
-            type: object
+            type: string
           examples:
             dataSample:
               $ref: 'components.yml#/components/examples/dataSample'
+        application/xml:
+          schema:
+            type: object
+            xml:
+              name: stores
+          examples:
+            dataSample:
+              $ref: 'components.yml#/components/examples/dataSampleXml'
     responses:
       '200':
         $ref: 'components.yml#/components/responses/Ok'
index cbb5ce4..d5a8ef3 100644 (file)
@@ -1,5 +1,5 @@
 # ============LICENSE_START=======================================================
-# Copyright (c) 2022-2023 TechMahindra Ltd.
+# Copyright (c) 2022-2024 TechMahindra Ltd.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ nodeByDataspaceAndAnchor:
         $ref: 'components.yml#/components/responses/InternalServerError'
     x-codegen-request-body-name: xpath
 
-deltaByDataspaceAndAnchors:
+delta:
   get:
     description: Get delta between two anchors within a given dataspace
     tags:
@@ -55,7 +55,7 @@ deltaByDataspaceAndAnchors:
     operationId: getDeltaByDataspaceAndAnchors
     parameters:
       - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
-      - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+      - $ref: 'components.yml#/components/parameters/sourceAnchorNameInPath'
       - $ref: 'components.yml#/components/parameters/targetAnchorNameInQuery'
       - $ref: 'components.yml#/components/parameters/xpathInQuery'
       - $ref: 'components.yml#/components/parameters/descendantsInQuery'
@@ -75,4 +75,53 @@ deltaByDataspaceAndAnchors:
         $ref: 'components.yml#/components/responses/Forbidden'
       '500':
         $ref: 'components.yml#/components/responses/InternalServerError'
-    x-codegen-request-body-name: xpath
\ No newline at end of file
+    x-codegen-request-body-name: xpath
+  post:
+    description: Get delta between an anchor in a dataspace and JSON payload
+    tags:
+      - cps-data
+    summary: Get delta between an anchor and JSON payload
+    operationId: getDeltaByDataspaceAnchorAndPayload
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+      - $ref: 'components.yml#/components/parameters/sourceAnchorNameInPath'
+      - $ref: 'components.yml#/components/parameters/xpathInQuery'
+    requestBody:
+      content:
+        multipart/form-data:
+          schema:
+            type: object
+            properties:
+              json:
+                type: object
+                example:
+                  test:bookstore:
+                    bookstore-name: Chapters
+                    categories:
+                      - code: 01
+                        name: SciFi
+                      - code: 02
+                        name: kids
+              file:
+                type: string
+                format: binary
+            required:
+              - json
+    responses:
+      '200':
+        description: OK
+        content:
+          application/json:
+            schema:
+              type: object
+            examples:
+              dataSample:
+                $ref: 'components.yml#/components/examples/deltaReportSample'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '401':
+        $ref: 'components.yml#/components/responses/Unauthorized'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '500':
+        $ref: 'components.yml#/components/responses/InternalServerError'
\ No newline at end of file
index f29335a..70f06fb 100644 (file)
@@ -2,7 +2,7 @@
 #  Copyright (C) 2021-2023 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2021 Bell Canada.
-#  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
+#  Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
 #  ================================================================================
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ openapi: 3.0.3
 info:
   title: ONAP Open API v3 Configuration Persistence Service
   description: Configuration Persistence Service is a Model Driven Generic Database
-  version: "1.0.0"
+  version: "3.5.2"
   contact:
      name: ONAP
      url: "https://onap.readthedocs.io"
@@ -31,10 +31,6 @@ info:
   license:
       name: "Apache 2.0"
       url: "http://www.apache.org/licenses/LICENSE-2.0"
-  x-planned-retirement-date: "202212"
-  x-component: "Modeling"
-  x-logo:
-      url: "cps_logo.png"
 
 servers:
   - url: /cps/api
@@ -104,8 +100,8 @@ paths:
   /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes:
     $ref: 'cpsData.yml#/listElementByDataspaceAndAnchor'
 
-  /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/delta:
-    $ref: 'cpsDataV2.yml#/deltaByDataspaceAndAnchors'
+  /v2/dataspaces/{dataspace-name}/anchors/{source-anchor-name}/delta:
+    $ref: 'cpsDataV2.yml#/delta'
 
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
     $ref: 'cpsQueryV1Deprecated.yml#/nodesByDataspaceAndAnchorAndCpsPath'
index ccfb77f..4bcb01a 100644 (file)
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 4f9328b..6015e0e 100755 (executable)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2023 Nordix Foundation
- *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 
 package org.onap.cps.rest.controller;
 
+import static org.onap.cps.rest.utils.MultipartFileUtil.extractYangResourcesMap;
+
 import io.micrometer.core.annotation.Timed;
 import jakarta.validation.ValidationException;
 import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
@@ -46,9 +49,9 @@ import org.onap.cps.utils.PrefixResolver;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.RequestHeader;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
 
 @RestController
 @RequestMapping("${rest.api.cps-base-path}")
@@ -66,11 +69,10 @@ public class DataRestController implements CpsDataApi {
     @Override
     public ResponseEntity<String> createNode(final String apiVersion,
                                              final String dataspaceName, final String anchorName,
-                                             @RequestHeader(value = "Content-Type") final String contentTypeHeader,
+                                             final String contentTypeInHeader,
                                              final String nodeData, final String parentNodeXpath,
                                              final String observedTimestamp) {
-        final ContentType contentType = contentTypeHeader.contains(MediaType.APPLICATION_XML_VALUE) ? ContentType.XML
-                : ContentType.JSON;
+        final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
         if (isRootXpath(parentNodeXpath)) {
             cpsDataService.saveData(dataspaceName, anchorName, nodeData,
                     toOffsetDateTime(observedTimestamp), contentType);
@@ -93,9 +95,11 @@ public class DataRestController implements CpsDataApi {
     @Override
     public ResponseEntity<String> addListElements(final String apiVersion, final String dataspaceName,
                                                   final String anchorName, final String parentNodeXpath,
-                                                  final Object jsonData, final String observedTimestamp) {
+                                                  final String contentTypeInHeader, final String nodeData,
+                                                  final String observedTimestamp) {
+        final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
         cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath,
-                jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp));
+                nodeData, toOffsetDateTime(observedTimestamp), contentType);
         return new ResponseEntity<>(HttpStatus.CREATED);
     }
 
@@ -133,19 +137,23 @@ public class DataRestController implements CpsDataApi {
 
     @Override
     public ResponseEntity<Object> updateNodeLeaves(final String apiVersion, final String dataspaceName,
-        final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) {
+                                                   final String anchorName, final String contentTypeInHeader,
+                                                   final String nodeData, final String parentNodeXpath,
+                                                   final String observedTimestamp) {
+        final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
         cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath,
-                jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp));
+                nodeData, toOffsetDateTime(observedTimestamp), contentType);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
     @Override
-    public ResponseEntity<Object> replaceNode(final String apiVersion,
-        final String dataspaceName, final String anchorName,
-        final Object jsonData, final String parentNodeXpath, final String observedTimestamp) {
-        cpsDataService
-                .updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath,
-                        jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp));
+    public ResponseEntity<Object> replaceNode(final String apiVersion, final String dataspaceName,
+                                             final String anchorName, final String contentTypeInHeader,
+                                             final String nodeData, final String parentNodeXpath,
+                                              final String observedTimestamp) {
+        final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
+        cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath,
+                        nodeData, toOffsetDateTime(observedTimestamp), contentType);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
@@ -167,6 +175,27 @@ public class DataRestController implements CpsDataApi {
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
 
+    @Override
+    public ResponseEntity<Object> getDeltaByDataspaceAnchorAndPayload(final String dataspaceName,
+                                                                      final String sourceAnchorName,
+                                                                      final Object jsonPayload,
+                                                                      final String xpath,
+                                                                      final MultipartFile multipartFile) {
+        final FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
+
+        final Map<String, String> yangResourceMap;
+        if (multipartFile == null) {
+            yangResourceMap = Collections.emptyMap();
+        } else {
+            yangResourceMap = extractYangResourcesMap(multipartFile);
+        }
+        final Collection<DeltaReport> deltaReports = Collections.unmodifiableList(
+                cpsDataService.getDeltaByDataspaceAnchorAndPayload(dataspaceName, sourceAnchorName,
+                xpath, yangResourceMap, jsonPayload.toString(), fetchDescendantsOption));
+
+        return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaReports), HttpStatus.OK);
+    }
+
     @Override
     @Timed(value = "cps.data.controller.get.delta",
             description = "Time taken to get delta between anchors")
@@ -184,6 +213,10 @@ public class DataRestController implements CpsDataApi {
         return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaBetweenAnchors), HttpStatus.OK);
     }
 
+    private static ContentType getContentTypeFromHeader(final String contentTypeInHeader) {
+        return contentTypeInHeader.contains(MediaType.APPLICATION_XML_VALUE) ? ContentType.XML : ContentType.JSON;
+    }
+
     private static boolean isRootXpath(final String xpath) {
         return ROOT_XPATH.equals(xpath);
     }
index 12c9c4c..5241f61 100755 (executable)
@@ -4,7 +4,7 @@
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
- *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -41,7 +41,9 @@ import org.springframework.beans.factory.annotation.Value
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
+import org.springframework.mock.web.MockMultipartFile
 import org.springframework.test.web.servlet.MockMvc
+import org.springframework.web.multipart.MultipartFile
 import spock.lang.Shared
 import spock.lang.Specification
 
@@ -49,6 +51,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
@@ -101,6 +104,10 @@ class DataRestControllerSpec extends Specification {
     static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent')
         .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()
 
+    @Shared
+    static MultipartFile multipartYangFile = new MockMultipartFile("file", 'filename.yang', "text/plain", 'content'.getBytes())
+
+
     def setup() {
         dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName"
         dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName"
@@ -185,22 +192,25 @@ class DataRestControllerSpec extends Specification {
             def rootNodeXpath = '/'
         when: 'list-node endpoint is invoked with post (create) operation'
             def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
-                .contentType(MediaType.APPLICATION_JSON)
+                .contentType(contentType)
                 .param('xpath', rootNodeXpath )
-                .content(requestBodyJson)
+                .content(requestBody)
             if (observedTimestamp != null)
                 postRequestBuilder.param('observed-timestamp', observedTimestamp)
             def response = mvc.perform(postRequestBuilder).andReturn().response
         then: 'a created response is returned'
             response.status == expectedHttpStatus.value()
         then: 'the java API was called with the correct parameters'
-            expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedJsonData,
-                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
+            expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedData,
+                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
         where:
-            scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
-            'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.CREATED
-            'without observed-timestamp'      | null                           || 1                | HttpStatus.CREATED
-            'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
+            scenario                                            | observedTimestamp              | contentType                | requestBody     || expectedApiCount | expectedHttpStatus     | expectedData     | expectedContentType
+            'Content type JSON with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1                | HttpStatus.CREATED     | expectedJsonData | ContentType.JSON
+            'Content type JSON without observed-timestamp'      | null                           | MediaType.APPLICATION_JSON | requestBodyJson || 1                | HttpStatus.CREATED     | expectedJsonData | ContentType.JSON
+            'Content type JSON with invalid observed-timestamp' | 'invalid'                      | MediaType.APPLICATION_JSON | requestBodyJson || 0                | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON
+            'Content type XML with observed-timestamp'          | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML  | requestBodyXml  || 1                | HttpStatus.CREATED     | expectedXmlData  | ContentType.XML
+            'Content type XML without observed-timestamp'       | null                           | MediaType.APPLICATION_XML  | requestBodyXml  || 1                | HttpStatus.CREATED     | expectedXmlData  | ContentType.XML
+            'Content type XML with invalid observed-timestamp'  | 'invalid'                      | MediaType.APPLICATION_XML  | requestBodyXml  || 0                | HttpStatus.BAD_REQUEST | expectedXmlData  | ContentType.XML
     }
 
     def 'Save list elements #scenario.'() {
@@ -208,22 +218,25 @@ class DataRestControllerSpec extends Specification {
             def parentNodeXpath = 'parent node xpath'
         when: 'list-node endpoint is invoked with post (create) operation'
             def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
-                .contentType(MediaType.APPLICATION_JSON)
+                .contentType(contentType)
                 .param('xpath', parentNodeXpath)
-                .content(requestBodyJson)
+                .content(requestBody)
             if (observedTimestamp != null)
                 postRequestBuilder.param('observed-timestamp', observedTimestamp)
             def response = mvc.perform(postRequestBuilder).andReturn().response
         then: 'a created response is returned'
             response.status == expectedHttpStatus.value()
         then: 'the java API was called with the correct parameters'
-            expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedJsonData,
-                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
+            expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedData,
+                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
         where:
-            scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
-            'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.CREATED
-            'without observed-timestamp'      | null                           || 1                | HttpStatus.CREATED
-            'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
+            scenario                                            | observedTimestamp              | contentType                | requestBody     || expectedApiCount | expectedHttpStatus     | expectedData     | expectedContentType
+            'Content type JSON with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1                | HttpStatus.CREATED     | expectedJsonData | ContentType.JSON
+            'Content type JSON without observed-timestamp'      | null                           | MediaType.APPLICATION_JSON | requestBodyJson || 1                | HttpStatus.CREATED     | expectedJsonData | ContentType.JSON
+            'Content type JSON with invalid observed-timestamp' | 'invalid'                      | MediaType.APPLICATION_JSON | requestBodyJson || 0                | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON
+            'Content type XML with observed-timestamp'          | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML  | requestBodyXml  || 1                | HttpStatus.CREATED     | expectedXmlData  | ContentType.XML
+            'Content type XML without observed-timestamp'       | null                           | MediaType.APPLICATION_XML  | requestBodyXml  || 1                | HttpStatus.CREATED     | expectedXmlData  | ContentType.XML
+            'Content type XML with invalid observed-timestamp'  | 'invalid'                      | MediaType.APPLICATION_XML  | requestBodyXml  || 0                | HttpStatus.BAD_REQUEST | expectedXmlData  | ContentType.XML
     }
 
     def 'Get data node with leaves'() {
@@ -337,7 +350,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Get delta between two anchors'() {
         given: 'the service returns a list containing delta reports'
-            def deltaReports = new DeltaReportBuilder().actionAdd().withXpath('/bookstore').withSourceData('bookstore-name': 'Easons').withTargetData('bookstore-name': 'Easons').build()
+            def deltaReports = new DeltaReportBuilder().actionReplace().withXpath('some xpath').withSourceData('some key': 'some value').withTargetData('some key': 'some value').build()
             def xpath = 'some xpath'
             def endpoint = "$dataNodeBaseEndpointV2/anchors/sourceAnchor/delta"
             mockCpsDataService.getDeltaByDataspaceAndAnchors(dataspaceName, 'sourceAnchor', 'targetAnchor', xpath, OMIT_DESCENDANTS) >> [deltaReports]
@@ -350,7 +363,48 @@ class DataRestControllerSpec extends Specification {
         then: 'expected response code is returned'
             assert response.status == HttpStatus.OK.value()
         and: 'the response contains expected value'
-            assert response.contentAsString.contains("[{\"action\":\"add\",\"xpath\":\"/bookstore\",\"sourceData\":{\"bookstore-name\":\"Easons\"},\"targetData\":{\"bookstore-name\":\"Easons\"}}]")
+            assert response.contentAsString.contains("[{\"action\":\"replace\",\"xpath\":\"some xpath\",\"sourceData\":{\"some key\":\"some value\"},\"targetData\":{\"some key\":\"some value\"}}]")
+    }
+
+    def 'Get delta between anchor and JSON payload with multipart file'() {
+        given: 'sample delta report, xpath, yang model file and json payload'
+            def deltaReports = new DeltaReportBuilder().actionCreate().withXpath('some xpath').build()
+            def xpath = 'some xpath'
+            def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/delta"
+        and: 'the service layer returns a list containing delta reports'
+            mockCpsDataService.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, ['filename.yang':'content'], expectedJsonData, INCLUDE_ALL_DESCENDANTS) >> [deltaReports]
+        when: 'get delta request is performed using REST API'
+            def response =
+                    mvc.perform(multipart(endpoint)
+                            .file(multipartYangFile)
+                            .param("json", requestBodyJson)
+                            .param('xpath', xpath)
+                            .contentType(MediaType.MULTIPART_FORM_DATA))
+                            .andReturn().response
+        then: 'expected response code is returned'
+            assert response.status == HttpStatus.OK.value()
+        and: 'the response contains expected value'
+            assert response.contentAsString.contains("[{\"action\":\"create\",\"xpath\":\"some xpath\"}]")
+    }
+
+    def 'Get delta between anchor and JSON payload without multipart file'() {
+        given: 'sample delta report, xpath, and json payload'
+            def deltaReports = new DeltaReportBuilder().actionRemove().withXpath('some xpath').build()
+            def xpath = 'some xpath'
+            def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/delta"
+        and: 'the service layer returns a list containing delta reports'
+            mockCpsDataService.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, [:], expectedJsonData, INCLUDE_ALL_DESCENDANTS) >> [deltaReports]
+        when: 'get delta request is performed using REST API'
+            def response =
+                    mvc.perform(multipart(endpoint)
+                            .param("json", requestBodyJson)
+                            .param('xpath', xpath)
+                            .contentType(MediaType.MULTIPART_FORM_DATA))
+                            .andReturn().response
+        then: 'expected response code is returned'
+            assert response.status == HttpStatus.OK.value()
+        and: 'the response contains expected value'
+            assert response.contentAsString.contains("[{\"action\":\"remove\",\"xpath\":\"some xpath\"}]")
     }
 
     def 'Update data node leaves: #scenario.'() {
@@ -360,19 +414,22 @@ class DataRestControllerSpec extends Specification {
             def response =
                 mvc.perform(
                     patch(endpoint)
-                        .contentType(MediaType.APPLICATION_JSON)
-                        .content(requestBodyJson)
+                        .contentType(contentType)
+                        .content(requestBody)
                         .param('xpath', inputXpath)
                 ).andReturn().response
         then: 'the service method is invoked with expected parameters'
-            1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, null)
+            1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedData, null, expectedContentType)
         and: 'response status indicates success'
             response.status == HttpStatus.OK.value()
         where:
-            scenario               | inputXpath    || xpathServiceParameter
-            'root node by default' | ''            || '/'
-            'root node by choice'  | '/'           || '/'
-            'some xpath by parent' | '/some/xpath' || '/some/xpath'
+            scenario                             | inputXpath    | contentType                || xpathServiceParameter | requestBody     | expectedData        | expectedContentType
+            'JSON content: root node by default' | ''            | MediaType.APPLICATION_JSON || '/'                   | requestBodyJson | expectedJsonData    | ContentType.JSON
+            'JSON content: root node by choice'  | '/'           | MediaType.APPLICATION_JSON || '/'                   | requestBodyJson | expectedJsonData    | ContentType.JSON
+            'JSON content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_JSON || '/some/xpath'         | requestBodyJson | expectedJsonData    | ContentType.JSON
+            'XML content: root node by default'  | ''            | MediaType.APPLICATION_XML  || '/'                   | requestBodyXml  | expectedXmlData     | ContentType.XML
+            'XML content: root node by choice'   | '/'           | MediaType.APPLICATION_XML  || '/'                   | requestBodyXml  | expectedXmlData     | ContentType.XML
+            'XML content: some xpath by parent'  | '/some/xpath' | MediaType.APPLICATION_XML  || '/some/xpath'         | requestBodyXml  | expectedXmlData     | ContentType.XML
     }
 
     def 'Update data node leaves with observedTimestamp'() {
@@ -389,7 +446,7 @@ class DataRestControllerSpec extends Specification {
                 ).andReturn().response
         then: 'the service method is invoked with expected parameters'
             expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', expectedJsonData,
-                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
+                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
         and: 'response status indicates success'
             response.status == expectedHttpStatus.value()
         where:
@@ -405,19 +462,22 @@ class DataRestControllerSpec extends Specification {
             def response =
                 mvc.perform(
                     put(endpoint)
-                        .contentType(MediaType.APPLICATION_JSON)
-                        .content(requestBodyJson)
+                        .contentType(contentType)
+                        .content(requestBody)
                         .param('xpath', inputXpath))
                     .andReturn().response
         then: 'the service method is invoked with expected parameters'
-            1 * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, noTimestamp)
+            1 * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, xpathServiceParameter, expectedData, noTimestamp, expectedContentType)
         and: 'response status indicates success'
             response.status == HttpStatus.OK.value()
         where:
-            scenario               | inputXpath    || xpathServiceParameter
-            'root node by default' | ''            || '/'
-            'root node by choice'  | '/'           || '/'
-            'some xpath by parent' | '/some/xpath' || '/some/xpath'
+            scenario                             | inputXpath    | contentType                || xpathServiceParameter | requestBody     | expectedData     | expectedContentType
+            'JSON content: root node by default' | ''            | MediaType.APPLICATION_JSON || '/'                   | requestBodyJson | expectedJsonData | ContentType.JSON
+            'JSON content: root node by choice'  | '/'           | MediaType.APPLICATION_JSON || '/'                   | requestBodyJson | expectedJsonData | ContentType.JSON
+            'JSON content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_JSON || '/some/xpath'         | requestBodyJson | expectedJsonData | ContentType.JSON
+            'XML content: root node by default'  | ''            | MediaType.APPLICATION_XML  || '/'                   | requestBodyXml  | expectedXmlData  | ContentType.XML
+            'XML content: root node by choice'   | '/'           | MediaType.APPLICATION_XML  || '/'                   | requestBodyXml  | expectedXmlData  | ContentType.XML
+            'XML content: some xpath by parent'  | '/some/xpath' | MediaType.APPLICATION_XML  || '/some/xpath'         | requestBodyXml  | expectedXmlData  | ContentType.XML
     }
 
     def 'Update data node and descendants with observedTimestamp.'() {
@@ -434,7 +494,7 @@ class DataRestControllerSpec extends Specification {
                     .andReturn().response
         then: 'the service method is invoked with expected parameters'
             expectedApiCount * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, '/', expectedJsonData,
-                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
+                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
         and: 'response status indicates success'
             response.status == expectedHttpStatus.value()
         where:
@@ -504,4 +564,5 @@ class DataRestControllerSpec extends Specification {
             'without observed timestamp'        | null                              || 1                | HttpStatus.NO_CONTENT
             'with invalid observed timestamp'   | 'invalid'                         || 0                | HttpStatus.BAD_REQUEST
     }
+
 }
index 221e1a9..57e6528 100644 (file)
@@ -26,7 +26,7 @@
     <parent>\r
         <groupId>org.onap.cps</groupId>\r
         <artifactId>cps-parent</artifactId>\r
-        <version>3.5.0-SNAPSHOT</version>\r
+        <version>3.5.3-SNAPSHOT</version>\r
         <relativePath>../cps-parent/pom.xml</relativePath>\r
     </parent>\r
 \r
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.impl;
+package org.onap.cps.ri;
 
 import jakarta.transaction.Transactional;
 import java.util.Collection;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.SchemaSetEntity;
+import org.onap.cps.ri.repository.AnchorRepository;
+import org.onap.cps.ri.repository.DataspaceRepository;
+import org.onap.cps.ri.repository.SchemaSetRepository;
 import org.onap.cps.spi.CpsAdminPersistenceService;
-import org.onap.cps.spi.entities.AnchorEntity;
-import org.onap.cps.spi.entities.DataspaceEntity;
-import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DataspaceInUseException;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.Dataspace;
-import org.onap.cps.spi.repository.AnchorRepository;
-import org.onap.cps.spi.repository.DataspaceRepository;
-import org.onap.cps.spi.repository.SchemaSetRepository;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.stereotype.Component;
 
@@ -106,6 +106,12 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
         }
     }
 
+    @Override
+    public Anchor getAnchor(final String dataspaceName, final String anchorName) {
+        final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
+        return toAnchor(anchorEntity);
+    }
+
     @Override
     public Collection<Anchor> getAnchors(final String dataspaceName) {
         final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
@@ -114,7 +120,14 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
     }
 
     @Override
-    public Collection<Anchor> getAnchors(final String dataspaceName, final String schemaSetName) {
+    public Collection<Anchor> getAnchors(final String dataspaceName, final Collection<String> anchorNames) {
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        return anchorRepository.findAllByDataspaceAndNameIn(dataspaceEntity, anchorNames)
+                .stream().map(CpsAdminPersistenceServiceImpl::toAnchor).collect(Collectors.toSet());
+    }
+
+    @Override
+    public Collection<Anchor> getAnchorsBySchemaSetName(final String dataspaceName, final String schemaSetName) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final SchemaSetEntity schemaSetEntity = schemaSetRepository.getByDataspaceAndName(
             dataspaceEntity, schemaSetName);
@@ -124,7 +137,8 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
     }
 
     @Override
-    public Collection<Anchor> getAnchors(final String dataspaceName, final Collection<String> schemaSetNames) {
+    public Collection<Anchor> getAnchorsBySchemaSetNames(final String dataspaceName,
+                                                         final Collection<String> schemaSetNames) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         return anchorRepository.findAllByDataspaceAndSchemaSetNameIn(dataspaceEntity, schemaSetNames)
             .stream().map(CpsAdminPersistenceServiceImpl::toAnchor).collect(Collectors.toSet());
@@ -137,11 +151,6 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
                 inputModuleNames.size());
     }
 
-    @Override
-    public Anchor getAnchor(final String dataspaceName, final String anchorName) {
-        return toAnchor(getAnchorEntity(dataspaceName, anchorName));
-    }
-
     @Transactional
     @Override
     public void deleteAnchor(final String dataspaceName, final String anchorName) {
@@ -21,7 +21,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.impl;
+package org.onap.cps.ri;
 
 import static org.onap.cps.spi.PaginationOption.NO_PAGINATION;
 
@@ -48,12 +48,16 @@ import org.hibernate.StaleStateException;
 import org.onap.cps.cpspath.parser.CpsPathQuery;
 import org.onap.cps.cpspath.parser.CpsPathUtil;
 import org.onap.cps.cpspath.parser.PathParsingException;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.FragmentEntity;
+import org.onap.cps.ri.repository.AnchorRepository;
+import org.onap.cps.ri.repository.DataspaceRepository;
+import org.onap.cps.ri.repository.FragmentRepository;
+import org.onap.cps.ri.utils.SessionManager;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.PaginationOption;
-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.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.ConcurrencyException;
 import org.onap.cps.spi.exceptions.CpsAdminException;
@@ -62,10 +66,6 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
-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.utils.SessionManager;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.stereotype.Service;
@@ -21,7 +21,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.impl;
+package org.onap.cps.ri;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -48,21 +48,21 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.hibernate.exception.ConstraintViolationException;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.SchemaSetEntity;
+import org.onap.cps.ri.models.YangResourceEntity;
+import org.onap.cps.ri.models.YangResourceModuleReference;
+import org.onap.cps.ri.repository.DataspaceRepository;
+import org.onap.cps.ri.repository.ModuleReferenceRepository;
+import org.onap.cps.ri.repository.SchemaSetRepository;
+import org.onap.cps.ri.repository.YangResourceRepository;
 import org.onap.cps.spi.CpsModulePersistenceService;
-import org.onap.cps.spi.entities.DataspaceEntity;
-import org.onap.cps.spi.entities.SchemaSetEntity;
-import org.onap.cps.spi.entities.YangResourceEntity;
-import org.onap.cps.spi.entities.YangResourceModuleReference;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DuplicatedYangResourceException;
 import org.onap.cps.spi.exceptions.ModelValidationException;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
 import org.onap.cps.spi.model.SchemaSet;
-import org.onap.cps.spi.repository.DataspaceRepository;
-import org.onap.cps.spi.repository.ModuleReferenceRepository;
-import org.onap.cps.spi.repository.SchemaSetRepository;
-import org.onap.cps.spi.repository.YangResourceRepository;
 import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
@@ -241,6 +241,15 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
         return moduleReferenceRepository.identifyNewModuleReferences(moduleReferencesToCheck);
     }
 
+    @Override
+    public Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName,
+                                                                      final String anchorName,
+                                                                      final Map<String, String> parentAttributes,
+                                                                      final Map<String, String> childAttributes) {
+        return moduleReferenceRepository.findModuleReferences(dataspaceName, anchorName, parentAttributes,
+                childAttributes);
+    }
+
     private Set<YangResourceEntity> synchronizeYangResources(
         final Map<String, String> moduleReferenceNameToContentMap) {
         final Map<String, YangResourceEntity> checksumToEntityMap = moduleReferenceNameToContentMap.entrySet().stream()
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.entities;
+package org.onap.cps.ri.models;
 
 
 import jakarta.persistence.Column;
@@ -19,7 +19,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.entities;
+package org.onap.cps.ri.models;
 
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
@@ -19,7 +19,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.entities;
+package org.onap.cps.ri.models;
 
 import jakarta.persistence.CascadeType;
 import jakarta.persistence.Column;
@@ -17,7 +17,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.entities;
+package org.onap.cps.ri.models;
 
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.entities;
+package org.onap.cps.ri.models;
 
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.entities;
+package org.onap.cps.ri.models;
 
 import org.springframework.beans.factory.annotation.Value;
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.util.Collection;
 import java.util.Optional;
-import org.onap.cps.spi.entities.AnchorEntity;
-import org.onap.cps.spi.entities.DataspaceEntity;
-import org.onap.cps.spi.entities.SchemaSetEntity;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.SchemaSetEntity;
 import org.onap.cps.spi.exceptions.AnchorNotFoundException;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Modifying;
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.util.Optional;
-import org.onap.cps.spi.entities.DataspaceEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.util.Collection;
+import org.onap.cps.ri.models.FragmentEntity;
 import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.entities.FragmentEntity;
 
 public interface FragmentPrefetchRepository {
     Collection<FragmentEntity> prefetchDescendantsOfFragmentEntities(
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.sql.Connection;
 import java.util.Collection;
@@ -29,9 +29,9 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import lombok.RequiredArgsConstructor;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.FragmentEntity;
 import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.entities.AnchorEntity;
-import org.onap.cps.spi.entities.FragmentEntity;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementSetter;
 import org.springframework.jdbc.core.RowMapper;
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.PersistenceContext;
 import jakarta.persistence.Query;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
 import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.cpspath.parser.CpsPathPrefixType;
 import org.onap.cps.cpspath.parser.CpsPathQuery;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.FragmentEntity;
+import org.onap.cps.ri.utils.EscapeUtils;
 import org.onap.cps.spi.PaginationOption;
-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.exceptions.CpsPathException;
-import org.onap.cps.spi.utils.EscapeUtils;
 import org.springframework.stereotype.Component;
 
 @RequiredArgsConstructor
-@Slf4j
 @Component
 public class FragmentQueryBuilder {
-    private static final AnchorEntity ACROSS_ALL_ANCHORS = null;
 
     @PersistenceContext
     private EntityManager entityManager;
@@ -59,8 +55,14 @@ public class FragmentQueryBuilder {
      * @return a executable query object
      */
     public Query getQueryForAnchorAndCpsPath(final AnchorEntity anchorEntity, final CpsPathQuery cpsPathQuery) {
-        return getQueryForDataspaceOrAnchorAndCpsPath(anchorEntity.getDataspace(),
-                anchorEntity, cpsPathQuery, Collections.emptyList());
+        final StringBuilder sqlStringBuilder = new StringBuilder();
+        final Map<String, Object> queryParameters = new HashMap<>();
+
+        sqlStringBuilder.append("SELECT fragment.* FROM fragment");
+        addWhereClauseForAnchor(anchorEntity, sqlStringBuilder, queryParameters);
+        addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, false);
+
+        return getQuery(sqlStringBuilder.toString(), queryParameters, FragmentEntity.class);
     }
 
     /**
@@ -73,8 +75,18 @@ public class FragmentQueryBuilder {
     public Query getQueryForDataspaceAndCpsPath(final DataspaceEntity dataspaceEntity,
                                                 final CpsPathQuery cpsPathQuery,
                                                 final List<Long> anchorIdsForPagination) {
-        return getQueryForDataspaceOrAnchorAndCpsPath(dataspaceEntity, ACROSS_ALL_ANCHORS,
-                cpsPathQuery, anchorIdsForPagination);
+        final StringBuilder sqlStringBuilder = new StringBuilder();
+        final Map<String, Object> queryParameters = new HashMap<>();
+
+        sqlStringBuilder.append("SELECT fragment.* FROM fragment");
+        if (anchorIdsForPagination.isEmpty()) {
+            addWhereClauseForDataspace(dataspaceEntity, sqlStringBuilder, queryParameters);
+        } else {
+            addWhereClauseForAnchorIds(anchorIdsForPagination, sqlStringBuilder, queryParameters);
+        }
+        addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, true);
+
+        return getQuery(sqlStringBuilder.toString(), queryParameters, FragmentEntity.class);
     }
 
     /**
@@ -89,52 +101,52 @@ public class FragmentQueryBuilder {
                                                    final PaginationOption paginationOption) {
         final StringBuilder sqlStringBuilder = new StringBuilder();
         final Map<String, Object> queryParameters = new HashMap<>();
-        sqlStringBuilder.append("SELECT distinct(fragment.anchor_id) FROM fragment "
-                + "JOIN anchor ON anchor.id = fragment.anchor_id WHERE dataspace_id = :dataspaceId");
-        queryParameters.put("dataspaceId", dataspaceEntity.getId());
-        addAbsoluteParentXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters, ACROSS_ALL_ANCHORS);
-        addXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
-        addLeafConditions(cpsPathQuery, sqlStringBuilder);
-        addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
-        addContainsFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
-        if (PaginationOption.NO_PAGINATION != paginationOption) {
-            sqlStringBuilder.append(" ORDER BY fragment.anchor_id");
-            addPaginationCondition(sqlStringBuilder, queryParameters, paginationOption);
-        }
 
-        final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString());
+        sqlStringBuilder.append("SELECT distinct(fragment.anchor_id) FROM fragment");
+        addWhereClauseForDataspace(dataspaceEntity, sqlStringBuilder, queryParameters);
+        addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, true);
+        sqlStringBuilder.append(" ORDER BY fragment.anchor_id");
+        addPaginationCondition(sqlStringBuilder, queryParameters, paginationOption);
+
+        return getQuery(sqlStringBuilder.toString(), queryParameters, Long.class);
+    }
+
+    private Query getQuery(final String sql, final Map<String, Object> queryParameters, final Class<?> returnType) {
+        final Query query = entityManager.createNativeQuery(sql, returnType);
         setQueryParameters(query, queryParameters);
         return query;
     }
 
-    private Query getQueryForDataspaceOrAnchorAndCpsPath(final DataspaceEntity dataspaceEntity,
-                                                         final AnchorEntity anchorEntity,
-                                                         final CpsPathQuery cpsPathQuery,
-                                                         final List<Long> anchorIdsForPagination) {
-        final StringBuilder sqlStringBuilder = new StringBuilder();
-        final Map<String, Object> queryParameters = new HashMap<>();
+    private static void addWhereClauseForAnchor(final AnchorEntity anchorEntity,
+                                                final StringBuilder sqlStringBuilder,
+                                                final Map<String, Object> queryParameters) {
+        sqlStringBuilder.append(" WHERE anchor_id = :anchorId");
+        queryParameters.put("anchorId", anchorEntity.getId());
+    }
 
-        if (anchorEntity == ACROSS_ALL_ANCHORS) {
-            sqlStringBuilder.append("SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id"
-                + " WHERE dataspace_id = :dataspaceId");
-            queryParameters.put("dataspaceId", dataspaceEntity.getId());
-            if (!anchorIdsForPagination.isEmpty()) {
-                sqlStringBuilder.append(" AND anchor_id IN (:anchorIdsForPagination)");
-                queryParameters.put("anchorIdsForPagination", anchorIdsForPagination);
-            }
-        } else {
-            sqlStringBuilder.append("SELECT * FROM fragment WHERE anchor_id = :anchorId");
-            queryParameters.put("anchorId", anchorEntity.getId());
-        }
-        addAbsoluteParentXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters, anchorEntity);
+    private static void addWhereClauseForAnchorIds(final List<Long> anchorIdsForPagination,
+                                                   final StringBuilder sqlStringBuilder,
+                                                   final Map<String, Object> queryParameters) {
+        sqlStringBuilder.append(" WHERE anchor_id IN (:anchorIdsForPagination)");
+        queryParameters.put("anchorIdsForPagination", anchorIdsForPagination);
+    }
+
+    private static void addWhereClauseForDataspace(final DataspaceEntity dataspaceEntity,
+                                                   final StringBuilder sqlStringBuilder,
+                                                   final Map<String, Object> queryParameters) {
+        sqlStringBuilder.append(" JOIN anchor ON anchor.id = fragment.anchor_id WHERE dataspace_id = :dataspaceId");
+        queryParameters.put("dataspaceId", dataspaceEntity.getId());
+    }
+
+    private static void addNodeSearchConditions(final CpsPathQuery cpsPathQuery,
+                                                final StringBuilder sqlStringBuilder,
+                                                final Map<String, Object> queryParameters,
+                                                final boolean acrossAnchors) {
+        addAbsoluteParentXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters, acrossAnchors);
         addXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
         addLeafConditions(cpsPathQuery, sqlStringBuilder);
         addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
         addContainsFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
-
-        final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class);
-        setQueryParameters(query, queryParameters);
-        return query;
     }
 
     private static void addXpathSearchCondition(final CpsPathQuery cpsPathQuery,
@@ -152,12 +164,12 @@ public class FragmentQueryBuilder {
     private static void addAbsoluteParentXpathSearchCondition(final CpsPathQuery cpsPathQuery,
                                                               final StringBuilder sqlStringBuilder,
                                                               final Map<String, Object> queryParameters,
-                                                              final AnchorEntity anchorEntity) {
+                                                              final boolean acrossAnchors) {
         if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) {
             if (cpsPathQuery.getNormalizedParentPath().isEmpty()) {
                 sqlStringBuilder.append(" AND parent_id IS NULL");
             } else {
-                if (anchorEntity == ACROSS_ALL_ANCHORS) {
+                if (acrossAnchors) {
                     sqlStringBuilder.append(" AND parent_id IN (SELECT id FROM fragment WHERE xpath = :parentXpath)");
                 } else {
                     sqlStringBuilder.append(" AND parent_id = (SELECT id FROM fragment WHERE xpath = :parentXpath"
@@ -171,10 +183,12 @@ public class FragmentQueryBuilder {
     private static void addPaginationCondition(final StringBuilder sqlStringBuilder,
                                                final Map<String, Object> queryParameters,
                                                final PaginationOption paginationOption) {
-        final Integer offset = (paginationOption.getPageIndex() - 1) * paginationOption.getPageSize();
-        sqlStringBuilder.append(" LIMIT :pageSize OFFSET :offset");
-        queryParameters.put("pageSize", paginationOption.getPageSize());
-        queryParameters.put("offset", offset);
+        if (PaginationOption.NO_PAGINATION != paginationOption) {
+            final Integer offset = (paginationOption.getPageIndex() - 1) * paginationOption.getPageSize();
+            sqlStringBuilder.append(" LIMIT :pageSize OFFSET :offset");
+            queryParameters.put("pageSize", paginationOption.getPageSize());
+            queryParameters.put("offset", offset);
+        }
     }
 
     private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) {
@@ -185,40 +199,34 @@ public class FragmentQueryBuilder {
         }
     }
 
-    private void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
+    private static void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
         if (cpsPathQuery.hasLeafConditions()) {
-            queryLeafConditions(cpsPathQuery, sqlStringBuilder);
-        }
-    }
-
-    private void queryLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
-        sqlStringBuilder.append(" AND (");
-        final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators());
-        final Queue<String> comparativeOperatorQueue = new LinkedList<>(cpsPathQuery.getComparativeOperators());
-        cpsPathQuery.getLeavesData().forEach(leaf -> {
-            final String nextComparativeOperator = comparativeOperatorQueue.poll();
-            if (leaf.getValue() instanceof Integer) {
-                sqlStringBuilder.append("(attributes ->> '").append(leaf.getName()).append("')\\:\\:int");
-                sqlStringBuilder.append(nextComparativeOperator);
-                sqlStringBuilder.append(leaf.getValue());
-            } else {
-                if ("=".equals(nextComparativeOperator)) {
-                    final String leafValueAsText = leaf.getValue().toString();
-                    sqlStringBuilder.append("attributes ->> '").append(leaf.getName()).append("'");
-                    sqlStringBuilder.append(" = '");
-                    sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText));
-                    sqlStringBuilder.append("'");
+            sqlStringBuilder.append(" AND (");
+            final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators());
+            cpsPathQuery.getLeafConditions().forEach(leafCondition -> {
+                if (leafCondition.value() instanceof Integer) {
+                    sqlStringBuilder.append("(attributes ->> '").append(leafCondition.name()).append("')\\:\\:int");
+                    sqlStringBuilder.append(leafCondition.operator());
+                    sqlStringBuilder.append(leafCondition.value());
                 } else {
-                    throw new CpsPathException(" can use only " + nextComparativeOperator + " with integer ");
+                    if ("=".equals(leafCondition.operator())) {
+                        final String leafValueAsText = leafCondition.value().toString();
+                        sqlStringBuilder.append("attributes ->> '").append(leafCondition.name()).append("'");
+                        sqlStringBuilder.append(" = '");
+                        sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText));
+                        sqlStringBuilder.append("'");
+                    } else {
+                        throw new CpsPathException(" can use only " + leafCondition.operator() + " with integer ");
+                    }
                 }
-            }
-            if (!booleanOperatorsQueue.isEmpty()) {
-                sqlStringBuilder.append(" ");
-                sqlStringBuilder.append(booleanOperatorsQueue.poll());
-                sqlStringBuilder.append(" ");
-            }
-        });
-        sqlStringBuilder.append(")");
+                if (!booleanOperatorsQueue.isEmpty()) {
+                    sqlStringBuilder.append(" ");
+                    sqlStringBuilder.append(booleanOperatorsQueue.poll());
+                    sqlStringBuilder.append(" ");
+                }
+            });
+            sqlStringBuilder.append(")");
+        }
     }
 
     private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery,
-/*\r
- * ============LICENSE_START=======================================================\r
- * Copyright (C) 2021-2023 Nordix Foundation.\r
- * Modifications Copyright (C) 2020-2021 Bell Canada.\r
- * Modifications Copyright (C) 2020-2021 Pantheon.tech.\r
- * Modifications Copyright (C) 2023 TechMahindra Ltd.\r
- * ================================================================================\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- *\r
- * SPDX-License-Identifier: Apache-2.0\r
- * ============LICENSE_END=========================================================\r
- */\r
-\r
-package org.onap.cps.spi.repository;\r
-\r
-import java.util.Collection;\r
-import java.util.List;\r
-import java.util.Optional;\r
-import org.onap.cps.spi.entities.AnchorEntity;\r
-import org.onap.cps.spi.entities.DataspaceEntity;\r
-import org.onap.cps.spi.entities.FragmentEntity;\r
-import org.onap.cps.spi.exceptions.DataNodeNotFoundException;\r
-import org.onap.cps.spi.utils.EscapeUtils;\r
-import org.springframework.data.jpa.repository.JpaRepository;\r
-import org.springframework.data.jpa.repository.Modifying;\r
-import org.springframework.data.jpa.repository.Query;\r
-import org.springframework.data.repository.query.Param;\r
-import org.springframework.stereotype.Repository;\r
-\r
-@Repository\r
-public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,\r
-        FragmentPrefetchRepository {\r
-\r
-    Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);\r
-\r
-    default FragmentEntity getByAnchorAndXpath(final AnchorEntity anchorEntity, final String xpath) {\r
-        return findByAnchorAndXpath(anchorEntity, xpath).orElseThrow(() ->\r
-            new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath));\r
-    }\r
-\r
-    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",\r
-            nativeQuery = true)\r
-    List<FragmentEntity> findByAnchorIdAndXpathIn(@Param("anchorId") long anchorId,\r
-                                                  @Param("xpaths") String[] xpaths);\r
-\r
-    default List<FragmentEntity> findByAnchorAndXpathIn(final AnchorEntity anchorEntity,\r
-                                                        final Collection<String> xpaths) {\r
-        return findByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0]));\r
-    }\r
-\r
-    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId \n"\r
-            + "AND xpath LIKE :escapedXpath||'[@%]' AND xpath NOT LIKE :escapedXpath||'[@%]/%[@%]'",\r
-            nativeQuery = true)\r
-    List<FragmentEntity> findListByAnchorIdAndEscapedXpath(@Param("anchorId") long anchorId,\r
-                                                           @Param("escapedXpath") String escapedXpath);\r
-\r
-    default List<FragmentEntity> findListByAnchorAndXpath(final AnchorEntity anchorEntity, final String xpath) {\r
-        final String escapedXpath = EscapeUtils.escapeForSqlLike(xpath);\r
-        return findListByAnchorIdAndEscapedXpath(anchorEntity.getId(), escapedXpath);\r
-    }\r
-\r
-    @Query(value = "SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id "\r
-        + "WHERE dataspace_id = :dataspaceId AND xpath = ANY (:xpaths)", nativeQuery = true)\r
-    List<FragmentEntity> findByDataspaceIdAndXpathIn(@Param("dataspaceId") int dataspaceId,\r
-                                                     @Param("xpaths") String[] xpaths);\r
-\r
-    default List<FragmentEntity> findByDataspaceAndXpathIn(final DataspaceEntity dataspaceEntity,\r
-                                                           final Collection<String> xpaths) {\r
-        return findByDataspaceIdAndXpathIn(dataspaceEntity.getId(), xpaths.toArray(new String[0]));\r
-    }\r
-\r
-    @Query(value = "SELECT * FROM fragment WHERE anchor_id IN (:anchorIds)"\r
-            + " AND xpath = ANY (:xpaths)", nativeQuery = true)\r
-    List<FragmentEntity> findByAnchorIdsAndXpathIn(@Param("anchorIds") Long[] anchorIds,\r
-                                                   @Param("xpaths") String[] xpaths);\r
-\r
-    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId LIMIT 1", nativeQuery = true)\r
-    Optional<FragmentEntity> findOneByAnchorId(@Param("anchorId") long anchorId);\r
-\r
-    @Modifying\r
-    @Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true)\r
-    void deleteByAnchorIdIn(@Param("anchorIds") long[] anchorIds);\r
-\r
-    default void deleteByAnchorIn(final Collection<AnchorEntity> anchorEntities) {\r
-        deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).mapToLong(id -> id).toArray());\r
-    }\r
-\r
-    @Modifying\r
-    @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", nativeQuery = true)\r
-    void deleteByAnchorIdAndXpaths(@Param("anchorId") long anchorId, @Param("xpaths") String[] xpaths);\r
-\r
-    default void deleteByAnchorIdAndXpaths(final long anchorId, final Collection<String> xpaths) {\r
-        deleteByAnchorIdAndXpaths(anchorId, xpaths.toArray(new String[0]));\r
-    }\r
-\r
-    @Modifying\r
-    @Query(value = "DELETE FROM fragment f WHERE anchor_id = :anchorId AND xpath LIKE ANY (:xpathPatterns)",\r
-        nativeQuery = true)\r
-    void deleteByAnchorIdAndXpathLikeAny(@Param("anchorId") long anchorId,\r
-                                         @Param("xpathPatterns") String[] xpathPatterns);\r
-\r
-    default void deleteListsByAnchorIdAndXpaths(long anchorId, Collection<String> xpaths) {\r
-        deleteByAnchorIdAndXpathLikeAny(anchorId,\r
-                xpaths.stream().map(xpath -> EscapeUtils.escapeForSqlLike(xpath) + "[@%").toArray(String[]::new));\r
-    }\r
-\r
-    @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",\r
-        nativeQuery = true)\r
-    List<String> findAllXpathByAnchorIdAndXpathIn(@Param("anchorId") long anchorId,\r
-                                                  @Param("xpaths") String[] xpaths);\r
-\r
-    default List<String> findAllXpathByAnchorAndXpathIn(final AnchorEntity anchorEntity,\r
-                                                        final Collection<String> xpaths) {\r
-        return findAllXpathByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0]));\r
-    }\r
-\r
-    @Query(value = "SELECT EXISTS(SELECT 1 FROM fragment WHERE anchor_id = :anchorId"\r
-            + " AND xpath LIKE :xpathPattern LIMIT 1)", nativeQuery = true)\r
-    boolean existsByAnchorIdAndParentXpathAndXpathLike(@Param("anchorId") long anchorId,\r
-                                                       @Param("xpathPattern") String xpathPattern);\r
-\r
-    default boolean existsByAnchorAndXpathStartsWith(final AnchorEntity anchorEntity, final String xpath) {\r
-        return existsByAnchorIdAndParentXpathAndXpathLike(anchorEntity.getId(),\r
-                EscapeUtils.escapeForSqlLike(xpath) + "%");\r
-    }\r
-\r
-    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND parent_id IS NULL", nativeQuery = true)\r
-    List<FragmentEntity> findRootsByAnchorId(@Param("anchorId") long anchorId);\r
-\r
-}\r
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2023 Nordix Foundation.
+ * Modifications Copyright (C) 2020-2021 Bell Canada.
+ * Modifications Copyright (C) 2020-2021 Pantheon.tech.
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
+ * ================================================================================
+ * 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.ri.repository;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.FragmentEntity;
+import org.onap.cps.ri.utils.EscapeUtils;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,
+        FragmentPrefetchRepository {
+
+    Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);
+
+    default FragmentEntity getByAnchorAndXpath(final AnchorEntity anchorEntity, final String xpath) {
+        return findByAnchorAndXpath(anchorEntity, xpath).orElseThrow(() ->
+            new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath));
+    }
+
+    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",
+            nativeQuery = true)
+    List<FragmentEntity> findByAnchorIdAndXpathIn(@Param("anchorId") long anchorId,
+                                                  @Param("xpaths") String[] xpaths);
+
+    default List<FragmentEntity> findByAnchorAndXpathIn(final AnchorEntity anchorEntity,
+                                                        final Collection<String> xpaths) {
+        return findByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0]));
+    }
+
+    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId \n"
+            + "AND xpath LIKE :escapedXpath||'[@%]' AND xpath NOT LIKE :escapedXpath||'[@%]/%[@%]'",
+            nativeQuery = true)
+    List<FragmentEntity> findListByAnchorIdAndEscapedXpath(@Param("anchorId") long anchorId,
+                                                           @Param("escapedXpath") String escapedXpath);
+
+    default List<FragmentEntity> findListByAnchorAndXpath(final AnchorEntity anchorEntity, final String xpath) {
+        final String escapedXpath = EscapeUtils.escapeForSqlLike(xpath);
+        return findListByAnchorIdAndEscapedXpath(anchorEntity.getId(), escapedXpath);
+    }
+
+    @Query(value = "SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id "
+        + "WHERE dataspace_id = :dataspaceId AND xpath = ANY (:xpaths)", nativeQuery = true)
+    List<FragmentEntity> findByDataspaceIdAndXpathIn(@Param("dataspaceId") int dataspaceId,
+                                                     @Param("xpaths") String[] xpaths);
+
+    default List<FragmentEntity> findByDataspaceAndXpathIn(final DataspaceEntity dataspaceEntity,
+                                                           final Collection<String> xpaths) {
+        return findByDataspaceIdAndXpathIn(dataspaceEntity.getId(), xpaths.toArray(new String[0]));
+    }
+
+    @Query(value = "SELECT * FROM fragment WHERE anchor_id IN (:anchorIds)"
+            + " AND xpath = ANY (:xpaths)", nativeQuery = true)
+    List<FragmentEntity> findByAnchorIdsAndXpathIn(@Param("anchorIds") Long[] anchorIds,
+                                                   @Param("xpaths") String[] xpaths);
+
+    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId LIMIT 1", nativeQuery = true)
+    Optional<FragmentEntity> findOneByAnchorId(@Param("anchorId") long anchorId);
+
+    @Modifying
+    @Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true)
+    void deleteByAnchorIdIn(@Param("anchorIds") long[] anchorIds);
+
+    default void deleteByAnchorIn(final Collection<AnchorEntity> anchorEntities) {
+        deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).mapToLong(id -> id).toArray());
+    }
+
+    @Modifying
+    @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", nativeQuery = true)
+    void deleteByAnchorIdAndXpaths(@Param("anchorId") long anchorId, @Param("xpaths") String[] xpaths);
+
+    default void deleteByAnchorIdAndXpaths(final long anchorId, final Collection<String> xpaths) {
+        deleteByAnchorIdAndXpaths(anchorId, xpaths.toArray(new String[0]));
+    }
+
+    @Modifying
+    @Query(value = "DELETE FROM fragment f WHERE anchor_id = :anchorId AND xpath LIKE ANY (:xpathPatterns)",
+        nativeQuery = true)
+    void deleteByAnchorIdAndXpathLikeAny(@Param("anchorId") long anchorId,
+                                         @Param("xpathPatterns") String[] xpathPatterns);
+
+    default void deleteListsByAnchorIdAndXpaths(long anchorId, Collection<String> xpaths) {
+        deleteByAnchorIdAndXpathLikeAny(anchorId,
+                xpaths.stream().map(xpath -> EscapeUtils.escapeForSqlLike(xpath) + "[@%").toArray(String[]::new));
+    }
+
+    @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",
+        nativeQuery = true)
+    List<String> findAllXpathByAnchorIdAndXpathIn(@Param("anchorId") long anchorId,
+                                                  @Param("xpaths") String[] xpaths);
+
+    default List<String> findAllXpathByAnchorAndXpathIn(final AnchorEntity anchorEntity,
+                                                        final Collection<String> xpaths) {
+        return findAllXpathByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0]));
+    }
+
+    @Query(value = "SELECT EXISTS(SELECT 1 FROM fragment WHERE anchor_id = :anchorId"
+            + " AND xpath LIKE :xpathPattern LIMIT 1)", nativeQuery = true)
+    boolean existsByAnchorIdAndParentXpathAndXpathLike(@Param("anchorId") long anchorId,
+                                                       @Param("xpathPattern") String xpathPattern);
+
+    default boolean existsByAnchorAndXpathStartsWith(final AnchorEntity anchorEntity, final String xpath) {
+        return existsByAnchorIdAndParentXpathAndXpathLike(anchorEntity.getId(),
+                EscapeUtils.escapeForSqlLike(xpath) + "%");
+    }
+
+    @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND parent_id IS NULL", nativeQuery = true)
+    List<FragmentEntity> findRootsByAnchorId(@Param("anchorId") long anchorId);
+
+}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.util.List;
 import org.onap.cps.cpspath.parser.CpsPathQuery;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.FragmentEntity;
 import org.onap.cps.spi.PaginationOption;
-import org.onap.cps.spi.entities.AnchorEntity;
-import org.onap.cps.spi.entities.DataspaceEntity;
-import org.onap.cps.spi.entities.FragmentEntity;
 
 public interface FragmentRepositoryCpsPathQuery {
     List<FragmentEntity> findByAnchorAndCpsPath(AnchorEntity anchorEntity, CpsPathQuery cpsPathQuery);
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation.
+ *  Copyright (C) 2021-2024 Nordix Foundation.
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.PersistenceContext;
 import jakarta.persistence.Query;
 import jakarta.transaction.Transactional;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.cpspath.parser.CpsPathQuery;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.FragmentEntity;
 import org.onap.cps.spi.PaginationOption;
-import org.onap.cps.spi.entities.AnchorEntity;
-import org.onap.cps.spi.entities.DataspaceEntity;
-import org.onap.cps.spi.entities.FragmentEntity;
 
 @RequiredArgsConstructor
 @Slf4j
 public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery {
 
-    @PersistenceContext
-    private EntityManager entityManager;
-
     private final FragmentQueryBuilder fragmentQueryBuilder;
 
     @Override
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation.
+ *  Copyright (C) 2022-2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.util.Collection;
+import java.util.Map;
 import org.onap.cps.spi.model.ModuleReference;
 
 /**
@@ -29,4 +30,8 @@ import org.onap.cps.spi.model.ModuleReference;
 public interface ModuleReferenceQuery {
 
     Collection<ModuleReference> identifyNewModuleReferences(final Collection<ModuleReference> moduleReferencesToCheck);
+
+    Collection<ModuleReference> findModuleReferences(final String dataspaceName, final String anchorName,
+                                                     final Map<String, String> parentAttributes,
+                                                     final Map<String, String> childAttributes);
 }
@@ -18,9 +18,9 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
-import org.onap.cps.spi.entities.YangResourceEntity;
+import org.onap.cps.ri.models.YangResourceEntity;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java
new file mode 100644 (file)
index 0000000..c160fb1
--- /dev/null
@@ -0,0 +1,179 @@
+/*-
+ * ============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.ri.repository;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.persistence.Query;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.model.ModuleReference;
+import org.springframework.transaction.annotation.Transactional;
+
+@Slf4j
+@Transactional
+@RequiredArgsConstructor
+public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    private final TempTableCreator tempTableCreator;
+
+    @Override
+    @SneakyThrows
+    public Collection<ModuleReference> identifyNewModuleReferences(
+            final Collection<ModuleReference> moduleReferencesToCheck) {
+
+        if (moduleReferencesToCheck == null || moduleReferencesToCheck.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final Collection<List<String>> sqlData = new HashSet<>(moduleReferencesToCheck.size());
+        for (final ModuleReference moduleReference : moduleReferencesToCheck) {
+            final List<String> row = new ArrayList<>(2);
+            row.add(moduleReference.getModuleName());
+            row.add(moduleReference.getRevision());
+            sqlData.add(row);
+        }
+
+        final String tempTableName = tempTableCreator.createTemporaryTable(
+            "moduleReferencesToCheckTemp", sqlData, "module_name", "revision");
+
+        return identifyNewModuleReferencesForCmHandle(tempTableName);
+    }
+
+    /**
+     * Finds module references based on specified dataspace, anchor, and attribute filters.
+     * This method constructs and executes a SQL query to retrieve module references. The query applies filters to
+     * parent and child fragments using the provided attribute maps. The `parentAttributes` are used to filter
+     * parent fragments, while `childAttributes` filter child fragments.
+     *
+     * @param dataspaceName    the name of the dataspace to filter on.
+     * @param anchorName       the name of the anchor to filter on.
+     * @param parentAttributes a map of attributes for filtering parent fragments.
+     * @param childAttributes  a map of attributes for filtering child fragments.
+     * @return a collection of {@link ModuleReference} objects that match the specified filters.
+     */
+    @Transactional
+    @SuppressWarnings("unchecked")
+    @Override
+    public Collection<ModuleReference> findModuleReferences(final String dataspaceName, final String anchorName,
+                                                            final Map<String, String> parentAttributes,
+                                                            final Map<String, String> childAttributes) {
+
+        final String parentFragmentWhereClause = buildWhereClause(childAttributes, "parentFragment");
+        final String childFragmentWhereClause = buildWhereClause(parentAttributes, "childFragment");
+
+        final String moduleReferencesSqlQuery = buildModuleReferencesSqlQuery(parentFragmentWhereClause,
+                childFragmentWhereClause);
+
+        final Query query = entityManager.createNativeQuery(moduleReferencesSqlQuery);
+        setQueryParameters(query, parentAttributes, childAttributes, anchorName, dataspaceName);
+        return processQueryResults(query.getResultList());
+    }
+
+    private String buildWhereClause(final Map<String, String> attributes, final String alias) {
+        return attributes.keySet().stream()
+                .map(attributeName -> String.format("%s.attributes->>'%s' = ?", alias, attributeName))
+                .collect(Collectors.joining(" AND "));
+    }
+
+    private void setQueryParameters(final Query query, final Map<String, String> parentAttributes,
+                                    final Map<String, String> childAttributes, final String anchorName,
+                                    final String dataspaceName) {
+        final String childAttributeValue = childAttributes.entrySet().iterator().next().getValue();
+        query.setParameter(1, childAttributeValue);
+
+        final String parentAttributeValue = parentAttributes.entrySet().iterator().next().getValue();
+        query.setParameter(2, parentAttributeValue);
+
+        query.setParameter(3, anchorName);
+        query.setParameter(4, dataspaceName);
+    }
+
+    private String buildModuleReferencesSqlQuery(final String parentFragmentClause, final String childFragmentClause) {
+        return """
+                WITH Fragment AS (
+                    SELECT childFragment.attributes->>'id' AS schema_set_name
+                    FROM fragment parentFragment
+                    JOIN fragment childFragment ON parentFragment.parent_id = childFragment.id
+                    JOIN anchor anchorInfo ON parentFragment.anchor_id = anchorInfo.id
+                    JOIN dataspace dataspaceInfo ON anchorInfo.dataspace_id = dataspaceInfo.id
+                    WHERE %s
+                    AND %s
+                    AND anchorInfo.name = ?
+                    AND dataspaceInfo.name = ?
+                    LIMIT 1
+                ),
+                SchemaSet AS (
+                    SELECT id
+                    FROM schema_set
+                    WHERE name = (SELECT schema_set_name FROM Fragment)
+                )
+                SELECT yangResource.module_name, yangResource.revision
+                FROM yang_resource yangResource
+                JOIN schema_set_yang_resources schemaSetYangResources
+                ON yangResource.id = schemaSetYangResources.yang_resource_id
+                WHERE schemaSetYangResources.schema_set_id = (SELECT id FROM SchemaSet);
+                """.formatted(parentFragmentClause, childFragmentClause);
+    }
+
+    private Collection<ModuleReference> processQueryResults(final List<Object[]> queryResults) {
+        if (queryResults.isEmpty()) {
+            log.info("No module references found for the provided attributes.");
+            return Collections.emptyList();
+        }
+        return queryResults.stream()
+                .map(queryResult -> {
+                    final String name = (String) queryResult[0];
+                    final String revision = (String) queryResult[1];
+                    return new ModuleReference(name, revision);
+                })
+                .collect(Collectors.toList());
+    }
+
+    private Collection<ModuleReference> identifyNewModuleReferencesForCmHandle(final String tempTableName) {
+        final String sql = String.format(
+                "SELECT %1$s.module_name, %1$s.revision"
+                        + " FROM %1$s LEFT JOIN yang_resource"
+                        + " ON yang_resource.module_name=%1$s.module_name"
+                        + " AND yang_resource.revision=%1$s.revision"
+                        + " WHERE yang_resource.module_name IS NULL;", tempTableName);
+
+        @SuppressWarnings("unchecked")
+        final List<Object[]> resultsAsObjects = entityManager.createNativeQuery(sql).getResultList();
+
+        final List<ModuleReference> resultsAsModuleReferences = new ArrayList<>(resultsAsObjects.size());
+        for (final Object[] row : resultsAsObjects) {
+            resultsAsModuleReferences.add(new ModuleReference((String) row[0], (String) row[1]));
+        }
+        return resultsAsModuleReferences;
+    }
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
-import org.onap.cps.spi.entities.DataspaceEntity;
-import org.onap.cps.spi.entities.SchemaSetEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.SchemaSetEntity;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Modifying;
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.PersistenceContext;
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.PersistenceContext;
@@ -31,7 +31,7 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.spi.utils.EscapeUtils;
+import org.onap.cps.ri.utils.EscapeUtils;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.PersistenceContext;
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.repository;
+package org.onap.cps.ri.repository;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
-import org.onap.cps.spi.entities.YangResourceEntity;
-import org.onap.cps.spi.entities.YangResourceModuleReference;
+import org.onap.cps.ri.models.YangResourceEntity;
+import org.onap.cps.ri.models.YangResourceModuleReference;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.config;
+package org.onap.cps.ri.utils;
 
 import org.hibernate.HibernateException;
 import org.hibernate.Session;
 import org.hibernate.SessionFactory;
-import org.onap.cps.spi.entities.AnchorEntity;
-import org.onap.cps.spi.entities.DataspaceEntity;
-import org.onap.cps.spi.entities.SchemaSetEntity;
-import org.onap.cps.spi.entities.YangResourceEntity;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.models.SchemaSetEntity;
+import org.onap.cps.ri.models.YangResourceEntity;
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.impl.utils;
+package org.onap.cps.ri.utils;
 
 import com.google.common.collect.Lists;
 import java.util.Arrays;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.PaginationOption;
 import org.onap.cps.spi.exceptions.DataValidationException;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.springframework.stereotype.Component;
 
 @Slf4j
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.utils;
+package org.onap.cps.ri.utils;
 
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.utils;
+package org.onap.cps.ri.utils;
 
 import com.google.common.util.concurrent.TimeLimiter;
 import com.google.common.util.concurrent.UncheckedExecutionException;
@@ -36,13 +36,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.hibernate.HibernateException;
 import org.hibernate.LockMode;
 import org.hibernate.Session;
-import org.onap.cps.spi.config.CpsSessionFactory;
-import org.onap.cps.spi.entities.AnchorEntity;
-import org.onap.cps.spi.entities.DataspaceEntity;
+import org.onap.cps.ri.models.AnchorEntity;
+import org.onap.cps.ri.models.DataspaceEntity;
+import org.onap.cps.ri.repository.AnchorRepository;
+import org.onap.cps.ri.repository.DataspaceRepository;
 import org.onap.cps.spi.exceptions.SessionManagerException;
 import org.onap.cps.spi.exceptions.SessionTimeoutException;
-import org.onap.cps.spi.repository.AnchorRepository;
-import org.onap.cps.spi.repository.DataspaceRepository;
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.utils;
+package org.onap.cps.ri.utils;
 
 import com.google.common.util.concurrent.SimpleTimeLimiter;
 import com.google.common.util.concurrent.TimeLimiter;
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java
deleted file mode 100644 (file)
index 454848b..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*-
- * ============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.spi.repository;
-
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.PersistenceContext;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.spi.model.ModuleReference;
-import org.springframework.transaction.annotation.Transactional;
-
-@Slf4j
-@Transactional
-@AllArgsConstructor
-public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
-
-    @PersistenceContext
-    private EntityManager entityManager;
-
-    private TempTableCreator tempTableCreator;
-
-    @Override
-    @SneakyThrows
-    public Collection<ModuleReference> identifyNewModuleReferences(
-            final Collection<ModuleReference> moduleReferencesToCheck) {
-
-        if (moduleReferencesToCheck == null || moduleReferencesToCheck.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        final Collection<List<String>> sqlData = new HashSet<>(moduleReferencesToCheck.size());
-        for (final ModuleReference moduleReference : moduleReferencesToCheck) {
-            final List<String> row = new ArrayList<>(2);
-            row.add(moduleReference.getModuleName());
-            row.add(moduleReference.getRevision());
-            sqlData.add(row);
-        }
-
-        final String tempTableName = tempTableCreator.createTemporaryTable(
-            "moduleReferencesToCheckTemp", sqlData, "module_name", "revision");
-
-        return identifyNewModuleReferencesForCmHandle(tempTableName);
-    }
-
-    private Collection<ModuleReference> identifyNewModuleReferencesForCmHandle(final String tempTableName) {
-        final String sql = String.format(
-                "SELECT %1$s.module_name, %1$s.revision"
-                        + " FROM %1$s LEFT JOIN yang_resource"
-                        + " ON yang_resource.module_name=%1$s.module_name"
-                        + " AND yang_resource.revision=%1$s.revision"
-                        + " WHERE yang_resource.module_name IS NULL;", tempTableName);
-
-        @SuppressWarnings("unchecked")
-        final List<Object[]> resultsAsObjects = entityManager.createNativeQuery(sql).getResultList();
-
-        final List<ModuleReference> resultsAsModuleReferences = new ArrayList<>(resultsAsObjects.size());
-        for (final Object[] row : resultsAsObjects) {
-            resultsAsModuleReferences.add(new ModuleReference((String) row[0], (String) row[1]));
-        }
-
-        return resultsAsModuleReferences;
-    }
-}
diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/dmi/generated-csv/README.md b/cps-ri/src/main/resources/changelog/db/changes/data/dmi/generated-csv/README.md
deleted file mode 100644 (file)
index 212acb9..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-  ============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=========================================================
--->
-
-##Placeholder folder for generated CSV files as part of yang models.
-
-Do not remove this folder
\ No newline at end of file
diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2021-12-13.yang b/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2021-12-13.yang
deleted file mode 100644 (file)
index ed3559b..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-module dmi-registry {
-
-  yang-version 1.1;
-
-  namespace "org:onap:cps:ncmp";
-
-  prefix dmi-reg;
-
-  contact "dylan.byrne@est.tech";
-
-  revision "2021-05-20" {
-    description
-    "Initial Version";
-  }
-
-  revision "2021-10-20" {
-    description
-    "Added dmi-data-service-name & dmi-model-service-name to allow separate DMI instances for each responsibility";
-  }
-
-  revision "2021-12-13" {
-    description
-    "Added new list of public additonal properties for a Cm-Handle which are exposed to clients of the NCMP interface";
-  }
-
-  container dmi-registry {
-    list cm-handles {
-      key "id";
-      leaf id {
-        type string;
-      }
-      leaf dmi-service-name {
-        type string;
-      }
-      leaf dmi-data-service-name {
-        type string;
-      }
-      leaf dmi-model-service-name {
-        type string;
-      }
-
-      list additional-properties {
-        key "name";
-        leaf name {
-          type string;
-        }
-        leaf value {
-          type string;
-        }
-      }
-
-      list public-properties {
-        key "name";
-        leaf name {
-          type string;
-        }
-        leaf value {
-          type string;
-        }
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-02-10.yang b/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-02-10.yang
deleted file mode 100644 (file)
index 3c6d990..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-module dmi-registry {
-
-  yang-version 1.1;
-
-  namespace "org:onap:cps:ncmp";
-
-  prefix dmi-reg;
-
-  contact "toine.siebelink@est.tech";
-
-  revision "2022-02-10" {
-    description
-    "Added State, LockReason, LockReasonDetails to aid with cmHandle sync and timestamp to aid with retry/timeout scenarios";
-  }
-
-  revision "2021-12-13" {
-    description
-    "Added new list of public additional properties for a Cm-Handle which are exposed to clients of the NCMP interface";
-  }
-
-  revision "2021-10-20" {
-    description
-    "Added dmi-data-service-name & dmi-model-service-name to allow separate DMI instances for each responsibility";
-  }
-
-  revision "2021-05-20" {
-    description
-    "Initial Version";
-  }
-
-  container dmi-registry {
-    list cm-handles {
-      key "id";
-      leaf id {
-        type string;
-      }
-      leaf dmi-service-name {
-        type string;
-      }
-      leaf dmi-data-service-name {
-        type string;
-      }
-      leaf dmi-model-service-name {
-        type string;
-      }
-
-      list additional-properties {
-        key "name";
-        leaf name {
-          type string;
-        }
-        leaf value {
-          type string;
-        }
-      }
-
-      list public-properties {
-        key "name";
-        leaf name {
-          type string;
-        }
-        leaf value {
-          type string;
-        }
-      }
-
-      leaf state {
-        type string;
-      }
-      leaf lock-reason {
-        type string;
-      }
-      leaf lock-reason-details {
-        type string;
-      }
-      leaf last-update-time {
-        type string;
-      }
-    }
-  }
-}
diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-05-10.yang b/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-05-10.yang
deleted file mode 100644 (file)
index 7751796..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-module dmi-registry {
-
-  yang-version 1.1;
-
-  namespace "org:onap:cps:ncmp";
-
-  prefix dmi-reg;
-
-  contact "toine.siebelink@est.tech";
-
-  revision "2022-05-10" {
-    description
-    "Added DataSyncEnabled, SyncState with State, LastSyncTime, DataStoreSyncState with Operational and Running syncstate";
-  }
-
-  revision "2022-02-10" {
-    description
-    "Added State, LockReason, LockReasonDetails to aid with cmHandle sync and timestamp to aid with retry/timeout scenarios";
-  }
-
-  revision "2021-12-13" {
-    description
-    "Added new list of public additional properties for a Cm-Handle which are exposed to clients of the NCMP interface";
-  }
-
-  revision "2021-10-20" {
-    description
-    "Added dmi-data-service-name & dmi-model-service-name to allow separate DMI instances for each responsibility";
-  }
-
-  revision "2021-05-20" {
-    description
-    "Initial Version";
-  }
-
-  grouping LockReason {
-    leaf reason {
-      type string;
-    }
-    leaf details {
-      type string;
-    }
-  }
-
-  grouping SyncState {
-   leaf sync-state {
-     type string;
-   }
-   leaf last-sync-time {
-     type string;
-   }
-  }
-
-  grouping Datastores {
-    container operational {
-      uses SyncState;
-    }
-    container running {
-      uses SyncState;
-    }
-  }
-
-  container dmi-registry {
-    list cm-handles {
-      key "id";
-      leaf id {
-        type string;
-      }
-      leaf dmi-service-name {
-        type string;
-      }
-      leaf dmi-data-service-name {
-        type string;
-      }
-      leaf dmi-model-service-name {
-        type string;
-      }
-
-      list additional-properties {
-        key "name";
-        leaf name {
-          type string;
-        }
-        leaf value {
-          type string;
-        }
-      }
-
-      list public-properties {
-        key "name";
-        leaf name {
-          type string;
-        }
-        leaf value {
-          type string;
-        }
-      }
-
-      container state {
-        leaf cm-handle-state {
-          type string;
-        }
-
-        container lock-reason {
-          uses LockReason;
-        }
-
-        leaf last-update-time {
-          type string;
-        }
-
-        leaf data-sync-enabled {
-          type boolean;
-          default "false";
-        }
-
-        container datastores {
-          uses Datastores;
-        }
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/cps-ri/src/main/resources/yangResourceCsvGenerator.py b/cps-ri/src/main/resources/yangResourceCsvGenerator.py
deleted file mode 100644 (file)
index 3a076d4..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-#  ============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=========================================================
-
-
-import csv
-import datetime
-import hashlib
-import sys
-import re
-
-yang_source = ''
-checksum = ''
-regexForModuleName = '(?<=module)(.*)(?={)'
-regexForRevision = '(?<=revision)(.*)(?={)'
-
-def main():
-    for yang_source in sys.argv[1:]:
-        checksum = hashlib.sha256(str(yang_source).encode()).hexdigest()
-
-        with open('changelog/db/changes/data/yang-models/' + yang_source + '.yang', 'r') as content:
-            dmiRegistry = content.read()
-
-        try:
-            latestRevision = re.search(regexForRevision, dmiRegistry).group(0).replace('\"','').strip()
-        except:
-            print("ERROR IN in yangResourceCsvGenerator.py: Unable to find revision for " + yang_source + '.yang')
-
-        try:
-            module_name = re.search(regexForModuleName, dmiRegistry).group(0).strip()
-        except:
-            print("ERROR IN in yangResourceCsvGenerator.py: Unable to find module name for " + yang_source + '.yang')
-
-        #If true, file was created after module_name and revision columns were added to yang-resources table
-        writeWithModuleNameAndRevision = yang_source != 'dmi-registry@2021-12-13'
-
-        try:
-            # open the file in the write mode
-            with open('changelog/db/changes/data/dmi/generated-csv/generated_yang_resource_' + yang_source + '.csv', 'w', newline='') \
-                    as file:
-                writer = csv.writer(file, delimiter='|')
-                if(writeWithModuleNameAndRevision):
-                    writer.writerow(["name", "content", "checksum", "module_name", "revision"])
-                    writer.writerow([yang_source + '.yang', dmiRegistry, checksum, module_name, latestRevision])
-                else:
-                    writer.writerow(["name", "content", "checksum"])
-                    writer.writerow([yang_source + '.yang', dmiRegistry, checksum])
-        except:
-            print("ERROR IN in yangResourceCsvGenerator.py: Unable to write to changelog/db/changes/data/dmi/generated-csv/generated_yang_resource_" + yang_source + ".csv")
-
-
-main()
\ No newline at end of file
  * ============LICENSE_END=========================================================
 */
 
-package org.onap.cps.spi.impl
+package org.onap.cps.ri
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.hibernate.StaleStateException
+import org.onap.cps.ri.models.AnchorEntity
+import org.onap.cps.ri.models.DataspaceEntity
+import org.onap.cps.ri.models.FragmentEntity
+import org.onap.cps.ri.repository.AnchorRepository
+import org.onap.cps.ri.repository.DataspaceRepository
+import org.onap.cps.ri.repository.FragmentRepository
+import org.onap.cps.ri.utils.SessionManager
 import org.onap.cps.spi.FetchDescendantsOption
-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.exceptions.ConcurrencyException
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
-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.utils.SessionManager
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.dao.DataIntegrityViolationException
 import spock.lang.Specification
+
 import java.util.stream.Collectors
 
-class CpsDataPersistenceServiceSpec extends Specification {
+class CpsDataPersistenceServiceImplSpec extends Specification {
 
     def mockDataspaceRepository = Mock(DataspaceRepository)
     def mockAnchorRepository = Mock(AnchorRepository)
  *  SPDX-License-Identifier: Apache-2.0
  *  ============LICENSE_END=========================================================
  */
-package org.onap.cps.spi.impl
+package org.onap.cps.ri
 
 import org.hibernate.exception.ConstraintViolationException
+import org.onap.cps.ri.models.DataspaceEntity
+import org.onap.cps.ri.models.SchemaSetEntity
+import org.onap.cps.ri.repository.DataspaceRepository
+import org.onap.cps.ri.repository.ModuleReferenceRepository
+import org.onap.cps.ri.repository.SchemaSetRepository
+import org.onap.cps.ri.repository.YangResourceRepository
 import org.onap.cps.spi.CpsAdminPersistenceService
 import org.onap.cps.spi.CpsModulePersistenceService
-import org.onap.cps.spi.entities.DataspaceEntity
-import org.onap.cps.spi.entities.SchemaSetEntity
 import org.onap.cps.spi.exceptions.DuplicatedYangResourceException
 import org.onap.cps.spi.model.ModuleReference
-import org.onap.cps.spi.repository.DataspaceRepository
-import org.onap.cps.spi.repository.ModuleReferenceRepository
-import org.onap.cps.spi.repository.SchemaSetRepository
-import org.onap.cps.spi.repository.YangResourceRepository
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
  * ============LICENSE_END=========================================================
 */
 
-package org.onap.cps.spi.impl
+package org.onap.cps.ri
 
 import org.hibernate.exception.ConstraintViolationException
+import org.onap.cps.ri.models.SchemaSetEntity
+import org.onap.cps.ri.repository.DataspaceRepository
+import org.onap.cps.ri.repository.ModuleReferenceRepository
+import org.onap.cps.ri.repository.SchemaSetRepository
+import org.onap.cps.ri.repository.YangResourceRepository
 import org.onap.cps.spi.CpsModulePersistenceService
-import org.onap.cps.spi.entities.SchemaSetEntity
 import org.onap.cps.spi.exceptions.DuplicatedYangResourceException
 import org.onap.cps.spi.model.ModuleReference
-import org.onap.cps.spi.repository.DataspaceRepository
-import org.onap.cps.spi.repository.ModuleReferenceRepository
-import org.onap.cps.spi.repository.SchemaSetRepository
-import org.onap.cps.spi.repository.YangResourceRepository
 import org.springframework.dao.DataIntegrityViolationException
 import spock.lang.Specification
+
 import java.sql.SQLException
 
 /**
  * Specification unit test class for CPS module persistence service.
  */
-class CpsModulePersistenceServiceSpec extends Specification {
+class CpsModulePersistenceServiceImplSpec extends Specification {
 
     CpsModulePersistenceService objectUnderTest
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.impl.utils
+package org.onap.cps.ri.utils
+
 
 import org.onap.cps.spi.PaginationOption
 import org.onap.cps.spi.exceptions.DataValidationException
 import spock.lang.Specification
 
-class CpsValidatorSpec extends Specification {
+class CpsValidatorImplSpec extends Specification {
 
     def objectUnderTest = new CpsValidatorImpl()
 
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.utils
+package org.onap.cps.ri.utils
 
 import spock.lang.Specification
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.utils
+package org.onap.cps.ri.utils
 
 import com.google.common.util.concurrent.TimeLimiter
 import com.google.common.util.concurrent.UncheckedExecutionException
 import org.hibernate.HibernateException
+import org.hibernate.Session
 import org.hibernate.Transaction
-import org.onap.cps.spi.config.CpsSessionFactory
-import org.onap.cps.spi.entities.AnchorEntity
+import org.onap.cps.ri.models.AnchorEntity
+import org.onap.cps.ri.repository.AnchorRepository
+import org.onap.cps.ri.repository.DataspaceRepository
 import org.onap.cps.spi.exceptions.SessionManagerException
-import org.onap.cps.spi.repository.AnchorRepository
-import org.onap.cps.spi.repository.DataspaceRepository
 import spock.lang.Specification
-import org.hibernate.Session
-import java.util.concurrent.ExecutionException
 
 class SessionManagerSpec extends Specification {
 
index 4224968..37a4595 100644 (file)
@@ -29,7 +29,7 @@
   <parent>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-parent</artifactId>
-    <version>3.5.0-SNAPSHOT</version>
+    <version>3.5.3-SNAPSHOT</version>
     <relativePath>../cps-parent/pom.xml</relativePath>
   </parent>
 
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
-    <dependency>
-      <groupId>io.cloudevents</groupId>
-      <artifactId>cloudevents-json-jackson</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>io.cloudevents</groupId>
-      <artifactId>cloudevents-kafka</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>io.cloudevents</groupId>
-      <artifactId>cloudevents-spring</artifactId>
-    </dependency>
     <!-- T E S T   D E P E N D E N C I E S -->
     <dependency>
       <groupId>org.codehaus.groovy</groupId>
index a247150..fcb969b 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -36,6 +36,15 @@ public interface CpsAnchorService {
      */
     void createAnchor(String dataspaceName, String schemaSetName, String anchorName);
 
+    /**
+     * Get an anchor in the given dataspace using the anchor name.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorName    anchor name
+     * @return an anchor
+     */
+    Anchor getAnchor(String dataspaceName, String anchorName);
+
     /**
      * Read all anchors in the given dataspace.
      *
@@ -44,6 +53,15 @@ public interface CpsAnchorService {
      */
     Collection<Anchor> getAnchors(String dataspaceName);
 
+    /**
+     * Read all anchors in the given dataspace with the anchor names.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorNames   anchor names
+     * @return a collection of anchors
+     */
+    Collection<Anchor> getAnchors(String dataspaceName, Collection<String> anchorNames);
+
     /**
      * Read all anchors associated with the given schema-set in the given dataspace.
      *
@@ -51,7 +69,7 @@ public interface CpsAnchorService {
      * @param schemaSetName schema-set name
      * @return a collection of anchors
      */
-    Collection<Anchor> getAnchors(String dataspaceName, String schemaSetName);
+    Collection<Anchor> getAnchorsBySchemaSetName(String dataspaceName, String schemaSetName);
 
     /**
      * Read all anchors associated with the given schema-sets in the given dataspace.
@@ -60,16 +78,7 @@ public interface CpsAnchorService {
      * @param schemaSetNames schema-set names
      * @return a collection of anchors
      */
-    Collection<Anchor> getAnchors(String dataspaceName, Collection<String> schemaSetNames);
-
-    /**
-     * Get an anchor in the given dataspace using the anchor name.
-     *
-     * @param dataspaceName dataspace name
-     * @param anchorName    anchor name
-     * @return an anchor
-     */
-    Anchor getAnchor(String dataspaceName, String anchorName);
+    Collection<Anchor> getAnchorsBySchemaSetNames(String dataspaceName, Collection<String> schemaSetNames);
 
     /**
      * Delete anchor by name in given dataspace.
index 0abcc05..68e1880 100644 (file)
@@ -4,7 +4,7 @@
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
- *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2023-2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ public interface CpsDataService {
      * @param anchorName    anchor name
      * @param nodeData      node data
      * @param observedTimestamp observedTimestamp
-     * @param contentType       node data content type
+     * @param contentType       JSON/XML content type
      */
     void saveData(String dataspaceName, String anchorName, String nodeData, OffsetDateTime observedTimestamp,
                   ContentType contentType);
@@ -80,7 +80,7 @@ public interface CpsDataService {
      * @param parentNodeXpath   parent node xpath
      * @param nodeData          node data
      * @param observedTimestamp observedTimestamp
-     * @param contentType       node data content type
+     * @param contentType       JSON/XML content type
      *
      */
     void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
@@ -93,11 +93,12 @@ public interface CpsDataService {
      * @param dataspaceName     dataspace name
      * @param anchorName        anchor name
      * @param parentNodeXpath   parent node xpath
-     * @param jsonData          json data representing list element(s)
+     * @param nodeData          node data representing list element(s)
      * @param observedTimestamp observedTimestamp
+     * @param contentType       JSON/XML content type
      */
-    void saveListElements(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData,
-        OffsetDateTime observedTimestamp);
+    void saveListElements(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+        OffsetDateTime observedTimestamp, ContentType contentType);
 
     /**
      * Retrieves all the datanodes by XPath for given dataspace and anchor.
@@ -132,11 +133,12 @@ public interface CpsDataService {
      * @param dataspaceName   dataspace name
      * @param anchorName      anchor name
      * @param parentNodeXpath xpath to parent node
-     * @param jsonData        json data
+     * @param nodeData        node data
      * @param observedTimestamp observedTimestamp
+     * @param contentType       JSON/XML content type
      */
-    void updateNodeLeaves(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData,
-        OffsetDateTime observedTimestamp);
+    void updateNodeLeaves(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+        OffsetDateTime observedTimestamp, ContentType contentType);
 
     /**
      * Replaces an existing data node's content including descendants.
@@ -144,22 +146,24 @@ public interface CpsDataService {
      * @param dataspaceName     dataspace name
      * @param anchorName        anchor name
      * @param parentNodeXpath   xpath to parent node
-     * @param jsonData          json data
+     * @param nodeData          node data
      * @param observedTimestamp observedTimestamp
+     * @param contentType       JSON/XML content type
      */
-    void updateDataNodeAndDescendants(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData,
-                                       OffsetDateTime observedTimestamp);
+    void updateDataNodeAndDescendants(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+                                       OffsetDateTime observedTimestamp, ContentType contentType);
 
     /**
      * Replaces multiple existing data nodes' content including descendants in a batch operation.
      *
      * @param dataspaceName   dataspace name
      * @param anchorName      anchor name
-     * @param nodesJsonData   map of xpath and node JSON data
+     * @param nodeDataPerXPath   map of xpath and node JSON/XML data
      * @param observedTimestamp observedTimestamp
+     * @param contentType       JSON/XML content type
      */
-    void updateDataNodesAndDescendants(String dataspaceName, String anchorName, Map<String, String> nodesJsonData,
-                                       OffsetDateTime observedTimestamp);
+    void updateDataNodesAndDescendants(String dataspaceName, String anchorName, Map<String, String> nodeDataPerXPath,
+                                       OffsetDateTime observedTimestamp, ContentType contentType);
 
     /**
      * Replaces list content by removing all existing elements and inserting the given new elements as json
@@ -302,4 +306,20 @@ public interface CpsDataService {
     List<DeltaReport> getDeltaByDataspaceAndAnchors(String dataspaceName, String sourceAnchorName,
                                                     String targetAnchorName, String xpath,
                                                     FetchDescendantsOption fetchDescendantsOption);
+
+    /**
+     * Retrieves the delta between an anchor and JSON payload by xpath, using dataspace name and anchor name.
+     *
+     * @param dataspaceName                     source dataspace name
+     * @param sourceAnchorName                  source anchor name
+     * @param xpath                             xpath
+     * @param yangResourcesNameToContentMap     YANG resources (files) map where key is a name and value is content
+     * @param targetData                        target data to be compared in JSON string format
+     * @param fetchDescendantsOption            defines the scope of data to fetch: defaulted to INCLUDE_ALL_DESCENDANTS
+     * @return                                  list containing {@link DeltaReport} objects
+     */
+    List<DeltaReport> getDeltaByDataspaceAnchorAndPayload(String dataspaceName, String sourceAnchorName, String xpath,
+                                                          Map<String, String> yangResourcesNameToContentMap,
+                                                          String targetData,
+                                                          FetchDescendantsOption fetchDescendantsOption);
 }
index bdd3614..931209c 100644 (file)
@@ -155,4 +155,37 @@ public interface CpsModuleService {
     Collection<ModuleReference> identifyNewModuleReferences(
         Collection<ModuleReference> moduleReferencesToCheck);
 
+    /**
+     * Retrieves module references based on the provided dataspace name, anchor name and attribute filters
+     * for both parent and child fragments.
+
+     * This method constructs and executes a SQL query to find module references from a database, using
+     * the specified `dataspaceName`, `anchorName` and two sets of attribute filters: one for parent fragments
+     * and one for child fragments. The method applies these filters to identify the appropriate fragments
+     * and schema sets, and then retrieves the corresponding module references.
+
+     * The SQL query is dynamically built based on the provided attribute filters:
+     * - The `parentAttributes` map is used to filter the parent fragments. The entries in this map are
+     * converted into a WHERE clause for the parent fragments.
+     * - The `childAttributes` map is used to filter the child fragments. This is applied to the child fragments
+     * after filtering the parent fragments.
+     *
+     * @param dataspaceName    the name of the dataspace to filter on. It is used to locate the relevant dataspace
+     *                         in the database.
+     * @param anchorName       the name of the anchor to filter on. It is used to locate the relevant anchor within
+     *                         the dataspace.
+     * @param parentAttributes a map of attributes to filter parent fragments. Each entry in this map represents
+     *                         an attribute key-value pair used in the WHERE clause for parent fragments.
+     * @param childAttributes  a map of attributes to filter child fragments. Each entry in this map represents
+     *                         an attribute key-value pair used in the WHERE clause for child fragments.
+     * @return a collection of {@link ModuleReference} objects that match the given criteria. Each
+     * {@code ModuleReference} contains information about a module's name and revision.
+     * @implNote The method assumes that both `parentAttributes` and `childAttributes` maps contain at least
+     *     one entry. The first entry from `parentAttributes` is used to filter parent fragments,
+     *     and the first entry from `childAttributes` is used to filter child fragments.
+     */
+    Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName, final String anchorName,
+                                                               final Map<String, String> parentAttributes,
+                                                               final Map<String, String> childAttributes);
+
 }
index aa9c45d..5ca0fe6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -23,10 +23,10 @@ package org.onap.cps.api.impl;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import org.onap.cps.api.CpsAnchorService;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.CpsAdminPersistenceService;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.model.Anchor;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -43,6 +43,12 @@ public class CpsAnchorServiceImpl implements CpsAnchorService {
         cpsAdminPersistenceService.createAnchor(dataspaceName, schemaSetName, anchorName);
     }
 
+    @Override
+    public Anchor getAnchor(final String dataspaceName, final String anchorName) {
+        cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+        return cpsAdminPersistenceService.getAnchor(dataspaceName, anchorName);
+    }
+
     @Override
     public Collection<Anchor> getAnchors(final String dataspaceName) {
         cpsValidator.validateNameCharacters(dataspaceName);
@@ -50,22 +56,24 @@ public class CpsAnchorServiceImpl implements CpsAnchorService {
     }
 
     @Override
-    public Collection<Anchor> getAnchors(final String dataspaceName, final String schemaSetName) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
-        return cpsAdminPersistenceService.getAnchors(dataspaceName, schemaSetName);
+    public Collection<Anchor> getAnchors(final String dataspaceName, final Collection<String> anchorNames) {
+        cpsValidator.validateNameCharacters(dataspaceName);
+        cpsValidator.validateNameCharacters(anchorNames);
+        return cpsAdminPersistenceService.getAnchors(dataspaceName, anchorNames);
     }
 
     @Override
-    public Collection<Anchor> getAnchors(final String dataspaceName, final Collection<String> schemaSetNames) {
-        cpsValidator.validateNameCharacters(dataspaceName);
-        cpsValidator.validateNameCharacters(schemaSetNames);
-        return cpsAdminPersistenceService.getAnchors(dataspaceName, schemaSetNames);
+    public Collection<Anchor> getAnchorsBySchemaSetName(final String dataspaceName, final String schemaSetName) {
+        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        return cpsAdminPersistenceService.getAnchorsBySchemaSetName(dataspaceName, schemaSetName);
     }
 
     @Override
-    public Anchor getAnchor(final String dataspaceName, final String anchorName) {
-        cpsValidator.validateNameCharacters(dataspaceName, anchorName);
-        return cpsAdminPersistenceService.getAnchor(dataspaceName, anchorName);
+    public Collection<Anchor> getAnchorsBySchemaSetNames(final String dataspaceName,
+                                                         final Collection<String> schemaSetNames) {
+        cpsValidator.validateNameCharacters(dataspaceName);
+        cpsValidator.validateNameCharacters(schemaSetNames);
+        return cpsAdminPersistenceService.getAnchorsBySchemaSetNames(dataspaceName, schemaSetNames);
     }
 
     @Override
index f556f40..951770b 100644 (file)
@@ -30,6 +30,7 @@ import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -41,6 +42,7 @@ import org.onap.cps.api.CpsDeltaService;
 import org.onap.cps.cpspath.parser.CpsPathUtil;
 import org.onap.cps.events.CpsDataUpdateEventsService;
 import org.onap.cps.events.model.Data.Operation;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.exceptions.DataValidationException;
@@ -48,8 +50,10 @@ import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
 import org.onap.cps.spi.model.DeltaReport;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.onap.cps.utils.ContentType;
+import org.onap.cps.utils.DataMapUtils;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.onap.cps.utils.PrefixResolver;
 import org.onap.cps.utils.YangParser;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.springframework.stereotype.Service;
@@ -61,6 +65,7 @@ public class CpsDataServiceImpl implements CpsDataService {
 
     private static final String ROOT_NODE_XPATH = "/";
     private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
+    private static final String NO_DATA_NODES = "No data nodes.";
 
     private final CpsDataPersistenceService cpsDataPersistenceService;
     private final CpsDataUpdateEventsService cpsDataUpdateEventsService;
@@ -69,6 +74,8 @@ public class CpsDataServiceImpl implements CpsDataService {
     private final CpsValidator cpsValidator;
     private final YangParser yangParser;
     private final CpsDeltaService cpsDeltaService;
+    private final JsonObjectMapper jsonObjectMapper;
+    private final PrefixResolver prefixResolver;
 
     @Override
     public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
@@ -83,7 +90,8 @@ public class CpsDataServiceImpl implements CpsDataService {
                          final OffsetDateTime observedTimestamp, final ContentType contentType) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
-        final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType);
+        final Collection<DataNode> dataNodes =
+                buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
         cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
         sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.CREATE, observedTimestamp);
     }
@@ -102,7 +110,8 @@ public class CpsDataServiceImpl implements CpsDataService {
                          final ContentType contentType) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
-        final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType);
+        final Collection<DataNode> dataNodes =
+                buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
         cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp);
     }
@@ -110,12 +119,13 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     @Timed(value = "cps.data.service.list.element.save",
         description = "Time taken to save list elements")
-    public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-                                 final String jsonData, final OffsetDateTime observedTimestamp) {
+    public void saveListElements(final String dataspaceName, final String anchorName,
+                                 final String parentNodeXpath, final String nodeData,
+                                 final OffsetDateTime observedTimestamp, final ContentType contentType) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
         final Collection<DataNode> listElementDataNodeCollection =
-            buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
+            buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
         if (isRootNodeXpath(parentNodeXpath)) {
             cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
         } else {
@@ -150,11 +160,11 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Timed(value = "cps.data.service.datanode.leaves.update",
         description = "Time taken to update a batch of leaf data nodes")
     public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-        final String jsonData, final OffsetDateTime observedTimestamp) {
+        final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
-        final Collection<DataNode> dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData,
-                ContentType.JSON);
+        final Collection<DataNode> dataNodesInPatch =
+                buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
         final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
                 .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
         cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
@@ -171,7 +181,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
         final Collection<DataNode> dataNodeUpdates =
-            buildDataNodes(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
+            buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
             processDataNodeUpdate(anchor, dataNodeUpdate);
         }
@@ -215,15 +225,39 @@ public class CpsDataServiceImpl implements CpsDataService {
         return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
     }
 
+    @Timed(value = "cps.data.service.get.deltaBetweenAnchorAndPayload",
+            description = "Time taken to get delta between anchor and a payload")
+    @Override
+    public List<DeltaReport> getDeltaByDataspaceAnchorAndPayload(final String dataspaceName,
+                                                                final String sourceAnchorName, final String xpath,
+                                                                final Map<String, String> yangResourcesNameToContentMap,
+                                                                final String targetData,
+                                                                final FetchDescendantsOption fetchDescendantsOption) {
+
+        final Anchor sourceAnchor = cpsAnchorService.getAnchor(dataspaceName, sourceAnchorName);
+
+        final Collection<DataNode> sourceDataNodes = getDataNodes(dataspaceName,
+                sourceAnchorName, xpath, fetchDescendantsOption);
+
+        final Collection<DataNode> sourceDataNodesRebuilt =
+                new ArrayList<>(rebuildSourceDataNodes(xpath, sourceAnchor, sourceDataNodes));
+
+        final Collection<DataNode> targetDataNodes =
+                new ArrayList<>(buildTargetDataNodes(sourceAnchor, xpath, yangResourcesNameToContentMap, targetData));
+
+        return cpsDeltaService.getDeltaReports(sourceDataNodesRebuilt, targetDataNodes);
+    }
+
     @Override
     @Timed(value = "cps.data.service.datanode.descendants.update",
         description = "Time taken to update a data node and descendants")
     public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
-                                             final String parentNodeXpath, final String jsonData,
-                                             final OffsetDateTime observedTimestamp) {
+                                             final String parentNodeXpath, final String nodeData,
+                                             final OffsetDateTime observedTimestamp, final ContentType contentType) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
-        final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
+        final Collection<DataNode> dataNodes =
+                buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
         sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
     }
@@ -232,13 +266,13 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Timed(value = "cps.data.service.datanode.descendants.batch.update",
         description = "Time taken to update a batch of data nodes and descendants")
     public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
-                                              final Map<String, String> nodesJsonData,
-                                              final OffsetDateTime observedTimestamp) {
+                                              final Map<String, String> nodeDataPerXPath,
+                                              final OffsetDateTime observedTimestamp, final ContentType contentType) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
-        final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData);
+        final Collection<DataNode> dataNodes = buildDataNodesWithParentNodeXpath(anchor, nodeDataPerXPath, contentType);
         cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
-        nodesJsonData.keySet().forEach(nodeXpath ->
+        nodeDataPerXPath.keySet().forEach(nodeXpath ->
                 sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp));
     }
 
@@ -250,7 +284,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
         final Collection<DataNode> newListElements =
-            buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
+            buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON);
         replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
     }
 
@@ -324,16 +358,69 @@ public class CpsDataServiceImpl implements CpsDataService {
         sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp);
     }
 
-    private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) {
+
+    private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor,
+                                                        final Collection<DataNode> sourceDataNodes) {
+
+        final Collection<DataNode> sourceDataNodesRebuilt = new ArrayList<>();
+        if (sourceDataNodes != null) {
+            final String sourceDataNodesAsJson = getDataNodesAsJson(sourceAnchor, sourceDataNodes);
+            sourceDataNodesRebuilt.addAll(
+                    buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
+        }
+        return sourceDataNodesRebuilt;
+    }
+
+    private Collection<DataNode> buildTargetDataNodes(final Anchor sourceAnchor, final String xpath,
+                                                      final Map<String, String> yangResourcesNameToContentMap,
+                                                      final String targetData) {
+        if (yangResourcesNameToContentMap.isEmpty()) {
+            return buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, targetData, ContentType.JSON);
+        } else {
+            return buildDataNodesWithYangResourceAndXpath(yangResourcesNameToContentMap, xpath,
+                    targetData, ContentType.JSON);
+        }
+    }
+
+    private String getDataNodesAsJson(final Anchor anchor, final Collection<DataNode> dataNodes) {
+
+        final List<Map<String, Object>> prefixToDataNodes = prefixResolver(anchor, dataNodes);
+        final Map<String, Object> targetDataAsJsonObject = getNodeDataAsJsonString(prefixToDataNodes);
+        return jsonObjectMapper.asJsonString(targetDataAsJsonObject);
+    }
+
+    private Map<String, Object> getNodeDataAsJsonString(final List<Map<String, Object>> prefixToDataNodes) {
+        final Map<String, Object>  nodeDataAsJson = new HashMap<>();
+        for (final Map<String, Object> prefixToDataNode : prefixToDataNodes) {
+            nodeDataAsJson.putAll(prefixToDataNode);
+        }
+        return nodeDataAsJson;
+    }
+
+    private List<Map<String, Object>> prefixResolver(final Anchor anchor, final Collection<DataNode> dataNodes) {
+        final List<Map<String, Object>> prefixToDataNodes = new ArrayList<>(dataNodes.size());
+        for (final DataNode dataNode: dataNodes) {
+            final String prefix = prefixResolver
+                    .getPrefix(anchor.getDataspaceName(), anchor.getName(), dataNode.getXpath());
+            final Map<String, Object> prefixToDataNode = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+            prefixToDataNodes.add(prefixToDataNode);
+        }
+        return prefixToDataNodes;
+    }
+
+    private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor,
+                                                                   final Map<String, String> nodesJsonData,
+                                                                   final ContentType contentType) {
         final Collection<DataNode> dataNodes = new ArrayList<>();
         for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
-            dataNodes.addAll(buildDataNodes(anchor, nodeJsonData.getKey(), nodeJsonData.getValue(), ContentType.JSON));
+            dataNodes.addAll(buildDataNodesWithParentNodeXpath(anchor, nodeJsonData.getKey(),
+                    nodeJsonData.getValue(), contentType));
         }
         return dataNodes;
     }
 
-    private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
-                                                final String nodeData, final ContentType contentType) {
+    private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor, final String parentNodeXpath,
+                                                                 final String nodeData, final ContentType contentType) {
 
         if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
             final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, "");
@@ -341,7 +428,7 @@ public class CpsDataServiceImpl implements CpsDataService {
                     .withContainerNode(containerNode)
                     .buildCollection();
             if (dataNodes.isEmpty()) {
-                throw new DataValidationException("No data nodes.", "No data nodes provided");
+                throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
             }
             return dataNodes;
         }
@@ -353,11 +440,67 @@ public class CpsDataServiceImpl implements CpsDataService {
             .withContainerNode(containerNode)
             .buildCollection();
         if (dataNodes.isEmpty()) {
-            throw new DataValidationException("No data nodes.", "No data nodes provided");
+            throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
+        }
+        return dataNodes;
+    }
+
+    private Collection<DataNode> buildDataNodesWithParentNodeXpath(
+                                          final Map<String, String> yangResourcesNameToContentMap, final String xpath,
+                                          final String nodeData, final ContentType contentType) {
+
+        if (isRootNodeXpath(xpath)) {
+            final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
+                    yangResourcesNameToContentMap, "");
+            final Collection<DataNode> dataNodes = new DataNodeBuilder()
+                    .withContainerNode(containerNode)
+                    .buildCollection();
+            if (dataNodes.isEmpty()) {
+                throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
+            }
+            return dataNodes;
+        }
+        final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(xpath);
+        final ContainerNode containerNode =
+                yangParser.parseData(contentType, nodeData, yangResourcesNameToContentMap, normalizedParentNodeXpath);
+        final Collection<DataNode> dataNodes = new DataNodeBuilder()
+                .withParentNodeXpath(normalizedParentNodeXpath)
+                .withContainerNode(containerNode)
+                .buildCollection();
+        if (dataNodes.isEmpty()) {
+            throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
         }
         return dataNodes;
     }
 
+    private Collection<DataNode> buildDataNodesWithAnchorAndXpath(final Anchor anchor, final String xpath,
+                                                                  final String nodeData,
+                                                                  final ContentType contentType) {
+
+        if (!isRootNodeXpath(xpath)) {
+            final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
+            if (parentNodeXpath.isEmpty()) {
+                return buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
+            }
+            return buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+        }
+        return buildDataNodesWithParentNodeXpath(anchor, xpath, nodeData, contentType);
+    }
+
+    private Collection<DataNode> buildDataNodesWithYangResourceAndXpath(
+                                            final Map<String, String> yangResourcesNameToContentMap, final String xpath,
+                                            final String nodeData, final ContentType contentType) {
+        if (!isRootNodeXpath(xpath)) {
+            final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
+            if (parentNodeXpath.isEmpty()) {
+                return buildDataNodesWithParentNodeXpath(yangResourcesNameToContentMap, ROOT_NODE_XPATH,
+                        nodeData, contentType);
+            }
+            return buildDataNodesWithParentNodeXpath(yangResourcesNameToContentMap, parentNodeXpath,
+                    nodeData, contentType);
+        }
+        return buildDataNodesWithParentNodeXpath(yangResourcesNameToContentMap, xpath, nodeData, contentType);
+    }
 
     private static boolean isRootNodeXpath(final String xpath) {
         return ROOT_NODE_XPATH.equals(xpath);
index a7f5da4..6bccf2a 100644 (file)
@@ -26,9 +26,9 @@ package org.onap.cps.api.impl;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import org.onap.cps.api.CpsDataspaceService;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.CpsAdminPersistenceService;
 import org.onap.cps.spi.model.Dataspace;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.springframework.stereotype.Service;
 
 @Service
index 2f99dbf..7819568 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.onap.cps.api.impl;
 
-
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -179,7 +178,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
                                                       final List<DeltaReport> updatedDeltaReportEntries) {
         for (final Map.Entry<Map<String, Serializable>, Map<String, Serializable>> entry:
                 updatedLeavesAsSourceDataToTargetData.entrySet()) {
-            final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionUpdate()
+            final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace()
                     .withXpath(xpath).withSourceData(entry.getKey()).withTargetData(entry.getValue()).build();
             updatedDeltaReportEntries.add(updatedDataForDeltaReport);
         }
@@ -195,7 +194,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
         for (final Map.Entry<String, DataNode> entry: xpathToAddedNodes.entrySet()) {
             final String xpath = entry.getKey();
             final DataNode dataNode = entry.getValue();
-            final DeltaReport addedDataForDeltaReport = new DeltaReportBuilder().actionAdd().withXpath(xpath)
+            final DeltaReport addedDataForDeltaReport = new DeltaReportBuilder().actionCreate().withXpath(xpath)
                                 .withTargetData(dataNode.getLeaves()).build();
             addedDeltaReportEntries.add(addedDataForDeltaReport);
         }
index 14b949e..a600b22 100644 (file)
@@ -30,6 +30,7 @@ import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsModuleService;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.CascadeDeleteAllowed;
 import org.onap.cps.spi.CpsModulePersistenceService;
 import org.onap.cps.spi.exceptions.SchemaSetInUseException;
@@ -37,7 +38,6 @@ import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
 import org.onap.cps.spi.model.SchemaSet;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder;
 import org.onap.cps.yang.YangTextSchemaSourceSet;
 import org.springframework.stereotype.Service;
@@ -97,7 +97,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     public void deleteSchemaSet(final String dataspaceName, final String schemaSetName,
                                 final CascadeDeleteAllowed cascadeDeleteAllowed) {
         cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
-        final Collection<Anchor> anchors = cpsAnchorService.getAnchors(dataspaceName, schemaSetName);
+        final Collection<Anchor> anchors = cpsAnchorService.getAnchorsBySchemaSetName(dataspaceName, schemaSetName);
         if (!anchors.isEmpty() && isCascadeDeleteProhibited(cascadeDeleteAllowed)) {
             throw new SchemaSetInUseException(dataspaceName, schemaSetName);
         }
@@ -114,8 +114,9 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     public void deleteSchemaSetsWithCascade(final String dataspaceName, final Collection<String> schemaSetNames) {
         cpsValidator.validateNameCharacters(dataspaceName);
         cpsValidator.validateNameCharacters(schemaSetNames);
-        final Collection<String> anchorNames = cpsAnchorService.getAnchors(dataspaceName, schemaSetNames)
-            .stream().map(Anchor::getName).collect(Collectors.toSet());
+        final Collection<String> anchorNames =
+                cpsAnchorService.getAnchorsBySchemaSetNames(dataspaceName, schemaSetNames)
+                        .stream().map(Anchor::getName).collect(Collectors.toSet());
         cpsAnchorService.deleteAnchors(dataspaceName, anchorNames);
         cpsModulePersistenceService.deleteSchemaSets(dataspaceName, schemaSetNames);
         cpsModulePersistenceService.deleteUnusedYangResourceModules();
@@ -170,6 +171,17 @@ public class CpsModuleServiceImpl implements CpsModuleService {
         return cpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck);
     }
 
+    @Timed(value = "cps.module.service.module.reference.query",
+            description = "Time taken to query list of module references")
+    @Override
+    public Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName,
+                                                                      final String anchorName,
+                                                                      final Map<String, String> parentAttributes,
+                                                                      final Map<String, String> childAttributes) {
+        return cpsModulePersistenceService.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes,
+                childAttributes);
+    }
+
     private boolean isCascadeDeleteProhibited(final CascadeDeleteAllowed cascadeDeleteAllowed) {
         return CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED == cascadeDeleteAllowed;
     }
index 1d7a7ce..d1c9898 100644 (file)
@@ -25,11 +25,11 @@ import io.micrometer.core.annotation.Timed;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import org.onap.cps.api.CpsQueryService;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.PaginationOption;
 import org.onap.cps.spi.model.DataNode;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.springframework.stereotype.Service;
 
 @Service
index 4fdae5a..8b85dfc 100644 (file)
@@ -27,8 +27,8 @@ import io.micrometer.core.instrument.Metrics;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import lombok.RequiredArgsConstructor;
+import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.spi.CpsModulePersistenceService;
-import org.onap.cps.spi.utils.CpsValidator;
 import org.onap.cps.yang.YangTextSchemaSourceSet;
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder;
 import org.springframework.cache.annotation.CacheConfig;
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.utils;
+package org.onap.cps.impl.utils;
 
 import org.onap.cps.spi.PaginationOption;
 
index 2b21619..25830a5 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation.
+ *  Copyright (C) 2020-2024 Nordix Foundation.
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -73,30 +73,48 @@ public interface CpsAdminPersistenceService {
     void createAnchor(String dataspaceName, String schemaSetName, String anchorName);
 
     /**
-     * Read all anchors associated with the given schema-set in the given dataspace.
+     * Get an anchor in the given dataspace using the anchor name.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     * @return an anchor
+     */
+    Anchor getAnchor(String dataspaceName, String anchorName);
+
+    /**
+     * Read all anchors in the given a dataspace.
      *
      * @param dataspaceName dataspace name
-     * @param schemaSetName schema-set name
      * @return a collection of anchors
      */
-    Collection<Anchor> getAnchors(String dataspaceName, String schemaSetName);
+    Collection<Anchor> getAnchors(String dataspaceName);
 
     /**
-     * Read all anchors associated with multiple schema-sets in the given dataspace.
+     * Read all anchors in the given dataspace with the anchor names.
      *
-     * @param dataspaceName  dataspace name
-     * @param schemaSetNames schema-set names
+     * @param dataspaceName dataspace name
+     * @param anchorNames   anchor names
      * @return a collection of anchors
      */
-    Collection<Anchor> getAnchors(String dataspaceName, Collection<String> schemaSetNames);
+    Collection<Anchor> getAnchors(String dataspaceName, Collection<String> anchorNames);
 
     /**
-     * Read all anchors in the given a dataspace.
+     * Read all anchors associated with the given schema-set in the given dataspace.
      *
      * @param dataspaceName dataspace name
+     * @param schemaSetName schema-set name
      * @return a collection of anchors
      */
-    Collection<Anchor> getAnchors(String dataspaceName);
+    Collection<Anchor> getAnchorsBySchemaSetName(String dataspaceName, String schemaSetName);
+
+    /**
+     * Read all anchors associated with multiple schema-sets in the given dataspace.
+     *
+     * @param dataspaceName  dataspace name
+     * @param schemaSetNames schema-set names
+     * @return a collection of anchors
+     */
+    Collection<Anchor> getAnchorsBySchemaSetNames(String dataspaceName, Collection<String> schemaSetNames);
 
     /**
      * Query anchor names for the given module names in the provided dataspace.
@@ -109,15 +127,6 @@ public interface CpsAdminPersistenceService {
      */
     Collection<String> queryAnchorNames(String dataspaceName, Collection<String> moduleNames);
 
-    /**
-     * Get an anchor in the given dataspace using the anchor name.
-     *
-     * @param dataspaceName dataspace name
-     * @param anchorName anchor name
-     * @return an anchor
-     */
-    Anchor getAnchor(String dataspaceName, String anchorName);
-
     /**
      * Delete anchor by name in given dataspace.
      *
index eeaaa47..793f38e 100755 (executable)
@@ -153,4 +153,20 @@ public interface CpsModulePersistenceService {
     Collection<ModuleReference> identifyNewModuleReferences(
         Collection<ModuleReference> moduleReferencesToCheck);
 
+    /**
+     * Retrieves module references based on the specified dataspace, anchor, and attribute filters.
+
+     * Constructs and executes a SQL query to find module references by applying filters for parent and child fragments.
+     * Uses `parentAttributes` for filtering parent fragments and `childAttributes` for filtering child fragments.
+     *
+     * @param dataspaceName    the name of the dataspace to filter on.
+     * @param anchorName       the name of the anchor to filter on.
+     * @param parentAttributes a map of attributes for filtering parent fragments.
+     * @param childAttributes  a map of attributes for filtering child fragments.
+     * @return a collection of {@link ModuleReference} objects matching the criteria.
+     */
+    Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName, final String anchorName,
+                                                               final Map<String, String> parentAttributes,
+                                                               final Map<String, String> childAttributes);
+
 }
index fb9c197..c6270a4 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.onap.cps.spi.model;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import java.io.Serializable;
 import java.util.Map;
 import lombok.AccessLevel;
@@ -28,11 +29,12 @@ import lombok.Setter;
 
 @Setter(AccessLevel.PROTECTED)
 @Getter
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class DeltaReport {
 
-    public static final String ADD_ACTION = "add";
+    public static final String CREATE_ACTION = "create";
     public static final String REMOVE_ACTION = "remove";
-    public static final String UPDATE_ACTION = "update";
+    public static final String REPLACE_ACTION = "replace";
 
     DeltaReport() {}
 
index 1e151ee..a7e7aae 100644 (file)
@@ -48,8 +48,8 @@ public class DeltaReportBuilder {
         return this;
     }
 
-    public DeltaReportBuilder actionAdd() {
-        this.action = DeltaReport.ADD_ACTION;
+    public DeltaReportBuilder actionCreate() {
+        this.action = DeltaReport.CREATE_ACTION;
         return this;
     }
 
@@ -58,8 +58,8 @@ public class DeltaReportBuilder {
         return this;
     }
 
-    public DeltaReportBuilder actionUpdate() {
-        this.action = DeltaReport.UPDATE_ACTION;
+    public DeltaReportBuilder actionReplace() {
+        this.action = DeltaReport.REPLACE_ACTION;
         return this;
     }
 
index 6299ef3..dc23c6b 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  * Copyright (C) 2024 Nordix Foundation.
+ * Modifications Copyright (C) 2024 TechMahindra Ltd.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 package org.onap.cps.utils;
 
 import io.micrometer.core.annotation.Timed;
+import java.util.Map;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.impl.YangTextSchemaSourceSetCache;
 import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.springframework.stereotype.Service;
 
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class YangParser {
 
     private final YangParserHelper yangParserHelper;
     private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache;
+    private final TimedYangTextSchemaSourceSetBuilder timedYangTextSchemaSourceSetBuilder;
 
     /**
      * Parses data into (normalized) ContainerNode according to schema context for the given anchor.
@@ -58,11 +64,33 @@ public class YangParser {
         return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
     }
 
+    /**
+     * Parses data into (normalized) ContainerNode according to schema context for the given yang resource.
+     *
+     * @param nodeData                         data string
+     * @param yangResourcesNameToContentMap    yang resource to content map
+     * @return                                 the NormalizedNode object
+     */
+    @Timed(value = "cps.utils.yangparser.nodedata.with.parent.with.yangResourceMap.parse",
+            description = "Time taken to parse node data with a parent")
+    public ContainerNode parseData(final ContentType contentType,
+                                   final String nodeData,
+                                   final Map<String, String> yangResourcesNameToContentMap,
+                                   final String parentNodeXpath) {
+        final SchemaContext schemaContext = getSchemaContext(yangResourcesNameToContentMap);
+        return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+    }
+
     private SchemaContext getSchemaContext(final Anchor anchor) {
         return yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(),
             anchor.getSchemaSetName()).getSchemaContext();
     }
 
+    private SchemaContext getSchemaContext(final Map<String, String> yangResourcesNameToContentMap) {
+        return timedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap)
+                .getSchemaContext();
+    }
+
     private void invalidateCache(final Anchor anchor) {
         yangTextSchemaSourceSetCache.removeFromCache(anchor.getDataspaceName(), anchor.getSchemaSetName());
     }
index 5cadd29..5971645 100644 (file)
@@ -120,13 +120,9 @@ public class YangParserHelper {
 
         try (jsonParserStream) {
             jsonParserStream.parse(jsonReader);
-        } catch (final IOException | JsonSyntaxException exception) {
+        } catch (final IOException | JsonSyntaxException | IllegalStateException | IllegalArgumentException exception) {
             throw new DataValidationException(
-                    "Failed to parse json data: " + jsonData, exception.getMessage(), exception);
-        } catch (final IllegalStateException | IllegalArgumentException exception) {
-            throw new DataValidationException(
-                    "Failed to parse json data. Unsupported xpath or json data:" + jsonData, exception
-                    .getMessage(), exception);
+                    "Data Validation Failed", "Failed to parse json data. " + exception.getMessage(), exception);
         }
         return dataContainerNodeBuilder.build();
     }
@@ -168,7 +164,7 @@ public class YangParserHelper {
         } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException
                        | ParserConfigurationException | TransformerException exception) {
             throw new DataValidationException(
-                "Failed to parse xml data: " + xmlData, exception.getMessage(), exception);
+                "Data Validation Failed", "Failed to parse xml data: " + exception.getMessage(), exception);
         }
         final DataContainerChild dataContainerChild =
             (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult());
index c786538..ccf943a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.api.impl
 
+import org.onap.cps.impl.utils.CpsValidator
 import org.onap.cps.spi.CpsAdminPersistenceService
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException
 import org.onap.cps.spi.model.Anchor
-import org.onap.cps.spi.utils.CpsValidator
 import spock.lang.Specification
 
 class CpsAnchorServiceImplSpec extends Specification {
@@ -59,9 +59,9 @@ class CpsAnchorServiceImplSpec extends Specification {
     def 'Retrieve all anchors for schema-set.'() {
         given: 'that anchor is associated with the dataspace and schemaset'
             def anchors = [new Anchor()]
-            mockCpsAdminPersistenceService.getAnchors('someDataspace', 'someSchemaSet') >> anchors
+            mockCpsAdminPersistenceService.getAnchorsBySchemaSetName('someDataspace', 'someSchemaSet') >> anchors
         when: 'get anchors is called for a dataspace name and schema set name'
-            def result = objectUnderTest.getAnchors('someDataspace', 'someSchemaSet')
+            def result = objectUnderTest.getAnchorsBySchemaSetName('someDataspace', 'someSchemaSet')
         then: 'the collection provided by persistence service is returned as result'
             result == anchors
         and: 'the CpsValidator is called on the dataspaceName, schemaSetName'
@@ -71,9 +71,9 @@ class CpsAnchorServiceImplSpec extends Specification {
     def 'Retrieve all anchors for multiple schema-sets.'() {
         given: 'that anchor is associated with the dataspace and schemasets'
             def anchors = [new Anchor(), new Anchor()]
-            mockCpsAdminPersistenceService.getAnchors('someDataspace', _ as Collection<String>) >> anchors
+            mockCpsAdminPersistenceService.getAnchorsBySchemaSetNames('someDataspace', _ as Collection<String>) >> anchors
         when: 'get anchors is called for a dataspace name and schema set names'
-            def result = objectUnderTest.getAnchors('someDataspace', ['schemaSet1', 'schemaSet2'])
+            def result = objectUnderTest.getAnchorsBySchemaSetNames('someDataspace', ['schemaSet1', 'schemaSet2'])
         then: 'the collection provided by persistence service is returned as result'
             result == anchors
         and: 'the CpsValidator is called on the dataspace name and schema-set names'
@@ -93,6 +93,21 @@ class CpsAnchorServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someAnchor')
     }
 
+    def 'Retrieve multiple anchors for dataspace and provided anchor names.'() {
+        given: 'multiple anchors names to get'
+            def anchorNames = ['anchor1', 'anchor2']
+        and: 'that anchors are associated with the dataspace and anchor names'
+            def anchors = [new Anchor(), new Anchor()]
+            mockCpsAdminPersistenceService.getAnchors('someDataspace', anchorNames) >> anchors
+        when: 'get anchors is called for a dataspace name and anchor names'
+            def result = objectUnderTest.getAnchors('someDataspace', anchorNames)
+        then: 'the collection provided by persistence service is returned as result'
+            result == anchors
+        and: 'the CpsValidator is called on the dataspace name and anchor names'
+            1 * mockCpsValidator.validateNameCharacters('someDataspace')
+            1 * mockCpsValidator.validateNameCharacters(anchorNames)
+    }
+
     def 'Delete anchor.'() {
         when: 'delete anchor is invoked'
             objectUnderTest.deleteAnchor('someDataspace','someAnchor')
@@ -105,15 +120,17 @@ class CpsAnchorServiceImplSpec extends Specification {
     }
 
     def 'Delete multiple anchors.'() {
+        given: 'multiple anchors to delete'
+            def anchorNames = ['anchor1', 'anchor2']
         when: 'delete anchors is invoked'
-            objectUnderTest.deleteAnchors('someDataspace', ['anchor1', 'anchor2'])
+            objectUnderTest.deleteAnchors('someDataspace', anchorNames)
         then: 'delete data nodes is invoked on the data service with expected parameters'
-            1 * mockCpsDataPersistenceService.deleteDataNodes('someDataspace', _ as Collection<String>)
+            1 * mockCpsDataPersistenceService.deleteDataNodes('someDataspace', anchorNames)
         and: 'the persistence service method is invoked with same parameters to delete anchor'
-            1 * mockCpsAdminPersistenceService.deleteAnchors('someDataspace',_ as Collection<String>)
+            1 * mockCpsAdminPersistenceService.deleteAnchors('someDataspace', anchorNames)
         and: 'the CpsValidator is called on the dataspace name and anchor names'
             1 * mockCpsValidator.validateNameCharacters('someDataspace')
-            1 * mockCpsValidator.validateNameCharacters(_)
+            1 * mockCpsValidator.validateNameCharacters(anchorNames)
     }
 
     def 'Query all anchor identifiers for a dataspace and module names.'() {
index fcbfd05..9846b30 100644 (file)
@@ -26,10 +26,12 @@ package org.onap.cps.api.impl
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
 import ch.qos.logback.core.read.ListAppender
+import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.TestUtils
 import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDeltaService
 import org.onap.cps.events.CpsDataUpdateEventsService
+import org.onap.cps.impl.utils.CpsValidator
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.exceptions.ConcurrencyException
@@ -39,28 +41,37 @@ import org.onap.cps.spi.exceptions.SessionManagerException
 import org.onap.cps.spi.exceptions.SessionTimeoutException
 import org.onap.cps.spi.model.Anchor
 import org.onap.cps.spi.model.DataNodeBuilder
-import org.onap.cps.spi.utils.CpsValidator
 import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.JsonObjectMapper
+import org.onap.cps.utils.PrefixResolver
 import org.onap.cps.utils.YangParser
 import org.onap.cps.utils.YangParserHelper
+import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
 import org.onap.cps.yang.YangTextSchemaSourceSet
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import org.slf4j.LoggerFactory
 import org.springframework.context.annotation.AnnotationConfigApplicationContext
 import spock.lang.Shared
 import spock.lang.Specification
+
 import java.time.OffsetDateTime
 
+import static org.onap.cps.events.model.Data.Operation.DELETE
+
 class CpsDataServiceImplSpec extends Specification {
     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
     def mockCpsAnchorService = Mock(CpsAnchorService)
     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
     def mockCpsValidator = Mock(CpsValidator)
-    def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache)
+    def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
+    def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder)
     def mockCpsDeltaService = Mock(CpsDeltaService);
     def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
+    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    def mockPrefixResolver = Mock(PrefixResolver)
 
-    def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService)
+    def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService,
+            mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
 
     def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
     def loggingListAppender
@@ -122,8 +133,8 @@ class CpsDataServiceImplSpec extends Specification {
         where: 'given parameters'
             scenario        | invalidData     | contentType      || expectedMessage
             'no data nodes' | '{}'            | ContentType.JSON || 'No data nodes'
-            'invalid json'  | '{invalid json' | ContentType.JSON || 'Failed to parse json data'
-            'invalid xml'   | '<invalid xml'  | ContentType.XML  || 'Failed to parse xml data'
+            'invalid json'  | '{invalid json' | ContentType.JSON || 'Data Validation Failed'
+            'invalid xml'   | '<invalid xml'  | ContentType.XML  || 'Data Validation Failed'
     }
 
     def 'Saving list element data fragment under Root node.'() {
@@ -131,7 +142,7 @@ class CpsDataServiceImplSpec extends Specification {
             setupSchemaSetMocks('bookstore.yang')
         when: 'save data method is invoked with list element json data'
             def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}'
-            objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp)
+            objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp, ContentType.JSON)
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
                 { dataNodeCollection ->
@@ -159,12 +170,11 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
     }
 
-    def 'Saving list element data fragment under existing node.'() {
+    def 'Saving list element data fragment under existing JSON/XML node.'() {
         given: 'schema set for given anchor and dataspace references test-tree model'
             setupSchemaSetMocks('test-tree.yang')
-        when: 'save data method is invoked with list element json data'
-            def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
-            objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+        when: 'save data method is invoked with list element data'
+            objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', data, observedTimestamp, contentType)
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
                 { dataNodeCollection ->
@@ -177,16 +187,23 @@ class CpsDataServiceImplSpec extends Specification {
             )
         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+        where:
+            data                                                                                                                        | contentType
+            '{"branch": [{"name": "A"}, {"name": "B"}]}'                                                                                | ContentType.JSON
+            '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch><branch><name>B</name></branch></test-tree>' | ContentType.XML
     }
 
-    def 'Saving empty list element data fragment.'() {
+    def 'Saving empty list element data fragment for JSON/XML data.'() {
         given: 'schema set for given anchor and dataspace references test-tree model'
             setupSchemaSetMocks('test-tree.yang')
         when: 'save data method is invoked with an empty list'
-            def jsonData = '{"branch": []}'
-            objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+            objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', data, observedTimestamp, contentType)
         then: 'invalid data exception is thrown'
             thrown(DataValidationException)
+        where:
+            data                                       | contentType
+            '{"branch": []}'                           | ContentType.JSON
+            '<test-tree><branch></branch></test-tree>' | ContentType.XML
     }
 
     def 'Get all data nodes #scenario.'() {
@@ -230,33 +247,89 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes)
     }
 
+    def 'Get delta between anchor and payload with user provided schema #scenario'() {
+        given: 'user provided schema set '
+            def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+            setupSchemaSetMocksForDelta(yangResourcesNameToContentMap)
+        when: 'attempt to get delta between an anchor and a JSON payload'
+            objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, yangResourcesNameToContentMap, jsonData, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+        then: 'dataspacename and anchor names are validated'
+            1 * mockCpsValidator.validateNameCharacters(['some-dataspace', 'some-anchor'])
+        and: 'source data nodes are fetched using appropriate persistence layer method'
+            1 * mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
+        and: 'appropriate delta service method is invoked once with correct source and target data nodes'
+            1 * mockCpsDeltaService.getDeltaReports({sourceDataNodesRebuilt -> sourceDataNodesRebuilt.xpath[0] == expectedNodeXpath}, {targetDataNodes -> targetDataNodes.xpath[0] == expectedNodeXpath})
+        where: 'following data was used'
+            scenario             | xpath                               | sourceDataNodes                                                                                          | jsonData                                       || expectedNodeXpath
+            'root node xpath'    | '/'                                 | [new DataNodeBuilder().withXpath('/bookstore').build()]                                                  | '{"bookstore":{"bookstore-name":"Easons"}}'    || '/bookstore'
+            'parent xpath'       | '/bookstore'                        | [new DataNodeBuilder().withXpath('/bookstore').build()]                                                  | '{"bookstore":{"bookstore-name":"Easons"}}'    || '/bookstore'
+            'non-root xpath'     | '/bookstore/categories[@code="02"]' | [new DataNodeBuilder().withXpath('/bookstore/categories[@code="02"]').withLeaves(["code":"02"]).build()] | '{"categories":[{"name":"kids","code":"02"}]}' || '/bookstore/categories[@code=\'02\']'
+    }
+
+    def 'Get delta between anchor and payload by using schema from anchor #scenario'() {
+        given: 'schema set for a given dataspace and anchor'
+            setupSchemaSetMocks("bookstore.yang")
+        when: 'attempt to get delta between an anchor and a JSON payload'
+            objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, [:], jsonData, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+        then: 'dataspacename and anchor names are validated'
+            1 * mockCpsValidator.validateNameCharacters(['some-dataspace', 'some-anchor'])
+        and: 'source data nodes are fetched using appropriate persistence layer method'
+            1 * mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
+        and: 'appropriate delta service method is invoked once with correct source and target data nodes'
+            1 * mockCpsDeltaService.getDeltaReports({sourceDataNodesRebuilt -> sourceDataNodesRebuilt.xpath[0] == expectedNodeXpath}, {targetDataNodes -> targetDataNodes.xpath[0] == expectedNodeXpath})
+        where: 'following data was used'
+            scenario          | xpath                               | sourceDataNodes                                                                                          | jsonData                                       || expectedNodeXpath
+            'root node xpath' | '/'                                 | [new DataNodeBuilder().withXpath('/bookstore').build()]                                                  | '{"bookstore":{"bookstore-name":"Easons"}}'    || '/bookstore'
+            'parent xpath'    | '/bookstore'                        | [new DataNodeBuilder().withXpath('/bookstore').build()]                                                  | '{"bookstore":{"bookstore-name":"Easons"}}'    || '/bookstore'
+            'non-root xpath'  | '/bookstore/categories[@code="02"]' | [new DataNodeBuilder().withXpath('/bookstore/categories[@code="02"]').withLeaves(["code":"02"]).build()] | '{"categories":[{"name":"kids","code":"02"}]}' || '/bookstore/categories[@code=\'02\']'
+    }
+
+    def 'Delta between anchor and payload error scenario #scenario'() {
+        given: 'schema set for given anchor and dataspace references bookstore model'
+            def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+            setupSchemaSetMocksForDelta(yangResourcesNameToContentMap)
+        when: 'attempt to get delta between anchor and payload'
+            objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, yangResourcesNameToContentMap, jsonData, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+        then: 'expected exception is thrown'
+            thrown(DataValidationException)
+        where: 'following parameters were used'
+            scenario                                   | xpath                               | jsonData
+            'invalid json data with root node xpath'   | '/'                                 | '{"some-key": "some-value"'
+            'empty json data with root node xpath'     | '/'                                 | '{}'
+            'invalid json data with parent node xpath' | '/bookstore'                        | '{"some-key": "some-value"'
+            'empty json data with parent node xpath'   | '/bookstore'                        | '{}'
+            'empty json data with xpath'               | "/bookstore/categories[@code='02']" | '{}'
+    }
+
     def 'Update data node leaves: #scenario.'() {
         given: 'schema set for given anchor and dataspace references test-tree model'
             setupSchemaSetMocks('test-tree.yang')
-        when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
-            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
+        when: 'update data method is invoked with node data #nodeData and parent node xpath #parentNodeXpath'
+            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, contentType)
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath})
         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
         where: 'following parameters were used'
-            scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
-            'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
-            'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
+            scenario                       | parentNodeXpath | nodeData                             || expectedNodeXpath                   | contentType
+            'JSON content: top level node' | '/'             | '{"test-tree": {"branch": []}}'      || '/test-tree'                        | ContentType.JSON
+            'JSON content: level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}'      || '/test-tree/branch[@name=\'Name\']' | ContentType.JSON
+            'XML  content: level 2 node'   | '/test-tree'    | '<branch><name>Name</name></branch>' || '/test-tree/branch[@name=\'Name\']' | ContentType.XML
     }
 
     def 'Update list-element data node with : #scenario.'() {
         given: 'schema set for given anchor and dataspace references bookstore model'
             setupSchemaSetMocks('bookstore.yang')
-        when: 'update data method is invoked with json data #jsonData and parent node xpath'
+        when: 'update data method is invoked with node data #nodeData and parent node xpath'
             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
-                jsonData, observedTimestamp)
+                nodeData, observedTimestamp, contentType)
         then: 'the persistence service method is invoked with correct parameters'
             thrown(DataValidationException)
         where: 'following parameters were used'
-            scenario                  | jsonData
-            'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
-            'one leaf'                | '{"name": "some-name"}'
+            scenario                                || nodeData                               | contentType
+            'JSON content: multiple expectedLeaves' || '{"code": "03","name": "some-name"}'   | ContentType.JSON
+            'JSON content: one leaf'                || '{"name": "some-name"}'                | ContentType.JSON
+            'XML content: multiple expectedLeaves'  || '<code>1</code><name>some-name</name>' | ContentType.XML
     }
 
     def 'Update data nodes in different containers.' () {
@@ -266,7 +339,7 @@ class CpsDataServiceImplSpec extends Specification {
             def parentNodeXpath = '/'
             def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}'
         when: 'update operation is performed on multiple data nodes'
-            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp)
+            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp, ContentType.JSON)
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath})
         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
@@ -296,11 +369,11 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
     }
 
-    def 'Replace data node using singular data node: #scenario.'() {
+    def 'Replace data node using singular JSON data node: #scenario.'() {
         given: 'schema set for given anchor and dataspace references test-tree model'
             setupSchemaSetMocks('test-tree.yang')
         when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
-            objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
+            objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp, ContentType.JSON)
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
                     { dataNode -> dataNode.xpath == expectedNodeXpath})
@@ -313,30 +386,63 @@ class CpsDataServiceImplSpec extends Specification {
             'json list'      | '/test-tree'    | '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}' || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
     }
 
-    def 'Replace data node using multiple data nodes: #scenario.'() {
+    def 'Replace data node using singular XML data node: #scenario.'() {
+        given: 'schema set for given anchor and dataspace references test-tree model'
+            setupSchemaSetMocks('test-tree.yang')
+        when: 'replace data method is invoked with XML data #xmlData and parent node xpath #parentNodeXpath'
+            objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, xmlData, observedTimestamp, ContentType.XML)
+        then: 'the persistence service method is invoked with correct parameters'
+            1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
+                { dataNode -> dataNode.xpath == expectedNodeXpath })
+        and: 'the CpsValidator is called on the dataspaceName and AnchorName'
+            1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+        where: 'following parameters were used'
+            scenario       | parentNodeXpath | xmlData                                                                                                                                  || expectedNodeXpath
+            'level 2 node' | '/test-tree'    | '<branch><name>Name</name></branch>'                                                                                                     || ['/test-tree/branch[@name=\'Name\']']
+            'xml list'     | '/test-tree'    | '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Name1</name></branch>' + '<branch><name>Name2</name></branch></test-tree>' || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
+    }
+
+    def 'Replace data node using multiple JSON data nodes: #scenario.'() {
         given: 'schema set for given anchor and dataspace references test-tree model'
             setupSchemaSetMocks('test-tree.yang')
         when: 'replace data method is invoked with a map of xpaths and json data'
-            objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
+            objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodeDataPerXPath, observedTimestamp, ContentType.JSON)
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
                 { dataNode -> dataNode.xpath == expectedNodeXpath})
         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
         where: 'following parameters were used'
-            scenario         | nodesJsonData                                                                                                        || expectedNodeXpath
+            scenario         | nodeDataPerXPath                                                                                                     || expectedNodeXpath
             'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}']                              || ["/test-tree", "/test-tree/branch[@name='Name']"]
             'level 2 node'   | ['/test-tree' : '{"branch": [{"name":"Name"}]}', '/test-tree/branch[@name=\'Name\']':'{"nest":{"name":"nestName"}}'] || ["/test-tree/branch[@name='Name']", "/test-tree/branch[@name='Name']/nest"]
             'json list'      | ['/test-tree' : '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}']                                                  || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
     }
 
+    def 'Replace data node using multiple XML data nodes: #scenario.'() {
+        given: 'schema set for given anchor and dataspace references test-tree model'
+            setupSchemaSetMocks('test-tree.yang')
+        when: 'replace data method is invoked with a map of xpaths and XML data'
+            objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodeXmlDataPerXPath, observedTimestamp, ContentType.XML)
+        then: 'the persistence service method is invoked with correct parameters'
+            1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
+                { dataNode -> dataNode.xpath == expectedNodeXpath })
+        and: 'the CpsValidator is called on the dataspaceName and AnchorName'
+            1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+        where: 'following parameters were used'
+            scenario         | nodeXmlDataPerXPath                                                                                                                                      || expectedNodeXpath
+            'top level node' | ['/test-tree': '<branch><name>Name</name></branch>']                                                                                                     || ["/test-tree/branch[@name='Name']"]
+            'level 2 node'   | ['/test-tree': '<branch><name>Name</name></branch>', '/test-tree/branch[@name=\'Name\']': '<nest><name>nestName</name></nest>']                          || ["/test-tree/branch[@name='Name']", "/test-tree/branch[@name='Name']/nest"]
+            'xml list'       | ['/test-tree': '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Name1</name></branch>' + '<branch><name>Name2</name></branch></test-tree>'] || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
+    }
+
     def 'Replace data node with concurrency exception in persistence layer.'() {
         given: 'the persistence layer throws an concurrency exception'
             def originalException = new ConcurrencyException('message', 'details')
             mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_) >> { throw originalException }
             setupSchemaSetMocks('test-tree.yang')
         when: 'attempt to replace data node'
-            objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp)
+            objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp, ContentType.JSON)
         then: 'the same exception is thrown up'
             def thrownUp = thrown(ConcurrencyException)
             assert thrownUp == originalException
@@ -425,15 +531,19 @@ class CpsDataServiceImplSpec extends Specification {
     def 'Delete all data nodes for given dataspace and multiple anchors.'() {
         given: 'schema set for given anchors and dataspace references test tree model'
             setupSchemaSetMocks('test-tree.yang')
-            mockCpsAnchorService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
-                [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
-                 new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
+            def anchor1 = new Anchor(name: 'anchor1', dataspaceName: dataspaceName)
+            def anchor2 = new Anchor(name: 'anchor2', dataspaceName: dataspaceName)
+            mockCpsAnchorService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >> [anchor1, anchor2]
         when: 'delete data node method is invoked with correct parameters'
             objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
         then: 'the CpsValidator is called on the dataspace name and the anchor names'
-            2 * mockCpsValidator.validateNameCharacters(_)
+            1 * mockCpsValidator.validateNameCharacters(dataspaceName)
+            1 * mockCpsValidator.validateNameCharacters(['anchor1', 'anchor2'])
         and: 'the persistence service method is invoked with the correct parameters'
             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
+        and: 'a data update event is sent for each anchor'
+            1 * mockDataUpdateEventsService.publishCpsDataUpdateEvent(anchor1, '/', DELETE, observedTimestamp)
+            1 * mockDataUpdateEventsService.publishCpsDataUpdateEvent(anchor2, '/', DELETE, observedTimestamp)
     }
 
     def 'Start session.'() {
@@ -486,7 +596,7 @@ class CpsDataServiceImplSpec extends Specification {
         when: 'publisher set to throw an exception'
             mockDataUpdateEventsService.publishCpsDataUpdateEvent(_, _, _, _) >> { throw new Exception("publishing failed")}
         and: 'an update event is performed'
-            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', '{"test-tree": {"branch": []}}', observedTimestamp)
+            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', '{"test-tree": {"branch": []}}', observedTimestamp, ContentType.JSON)
         then: 'the exception is not bubbled up'
             noExceptionThrown()
         and: "the exception message is logged"
@@ -501,4 +611,12 @@ class CpsDataServiceImplSpec extends Specification {
         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
     }
 
+    def setupSchemaSetMocksForDelta(Map<String, String> yangResourcesNameToContentMap) {
+        def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+        mockTimedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap) >> mockYangTextSchemaSourceSet
+        mockYangTextSchemaSourceSetCache.get(_, _) >> mockYangTextSchemaSourceSet
+        def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()
+        mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+    }
+
 }
index 8e17594..ac7d4c0 100644 (file)
@@ -20,9 +20,9 @@
 
 package org.onap.cps.api.impl
 
+import org.onap.cps.impl.utils.CpsValidator
 import org.onap.cps.spi.CpsAdminPersistenceService
 import org.onap.cps.spi.model.Dataspace
-import org.onap.cps.spi.utils.CpsValidator
 import spock.lang.Specification
 
 class CpsDataspaceServiceImplSpec extends Specification {
index 42d75f3..f12afe6 100644 (file)
@@ -51,8 +51,8 @@ class CpsDeltaServiceImplSpec extends Specification{
     def 'Get delta between data nodes for ADDED data'() {
         when: 'attempt to get delta between 2 data nodes'
             def result = objectUnderTest.getDeltaReports([], targetDataNodeWithLeafData)
-        then: 'the delta report contains expected "add" action'
-            assert result[0].action.equals('add')
+        then: 'the delta report contains expected "create" action'
+            assert result[0].action.equals('create')
         and: 'the delta report contains expected xpath'
             assert result[0].xpath == '/parent'
         and: 'the delta report contains no source data'
@@ -68,12 +68,12 @@ class CpsDeltaServiceImplSpec extends Specification{
         when: 'attempt to get delta between 2 data nodes'
             def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode)
         then: 'the delta report contains expected details for parent node'
-            assert result[0].action.equals('update')
+            assert result[0].action.equals('replace')
             assert result[0].xpath == '/parent'
             assert result[0].sourceData == ['parent-leaf': 'parent-payload']
             assert result[0].targetData == ['parent-leaf': 'parent-payload-updated']
         and: 'the delta report contains expected details for child node'
-            assert result[1].action.equals('update')
+            assert result[1].action.equals('replace')
             assert result[1].xpath == '/parent/child'
             assert result[1].sourceData == ['child-leaf': 'child-payload']
             assert result[1].targetData == ['child-leaf': 'child-payload-updated']
@@ -82,8 +82,8 @@ class CpsDeltaServiceImplSpec extends Specification{
     def 'Delta report between leaves, #scenario'() {
         when: 'attempt to get delta between 2 data nodes'
             def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode)
-        then: 'the delta report contains expected "update" action'
-            assert result[0].action.equals('update')
+        then: 'the delta report contains expected "replace" action'
+            assert result[0].action.equals('replace')
         and: 'the delta report contains expected xpath'
             assert result[0].xpath == '/parent'
         and: 'the delta report contains expected source and target data'
@@ -100,7 +100,7 @@ class CpsDeltaServiceImplSpec extends Specification{
     def 'Get delta between data nodes for updated data, where source and target data nodes have no leaves '() {
         when: 'attempt to get delta between 2 data nodes'
             def result = objectUnderTest.getDeltaReports(sourceDataNodeWithoutLeafData, targetDataNodeWithoutLeafData)
-        then: 'the delta report contains "update" action with right data'
+        then: 'the delta report is empty'
             assert result.isEmpty()
     }
 }
index 0bad0de..1831506 100644 (file)
 
 package org.onap.cps.api.impl
 
-import org.onap.cps.api.CpsAnchorService
-
-import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED
-import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
-
 import org.onap.cps.TestUtils
+import org.onap.cps.api.CpsAnchorService
+import org.onap.cps.impl.utils.CpsValidator
 import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.spi.exceptions.DuplicatedYangResourceException
 import org.onap.cps.spi.exceptions.ModelValidationException
 import org.onap.cps.spi.exceptions.SchemaSetInUseException
-import org.onap.cps.spi.model.ModuleDefinition
-import org.onap.cps.spi.utils.CpsValidator
 import org.onap.cps.spi.model.Anchor
+import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.spi.model.SchemaSet
 import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
@@ -43,6 +39,9 @@ import org.onap.cps.yang.YangTextSchemaSourceSet
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import spock.lang.Specification
 
+import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED
+import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
+
 class CpsModuleServiceImplSpec extends Specification {
 
     def mockCpsModulePersistenceService = Mock(CpsModulePersistenceService)
@@ -134,7 +133,7 @@ class CpsModuleServiceImplSpec extends Specification {
     def 'Delete schema-set when cascade is allowed.'() {
         given: '#numberOfAnchors anchors are associated with schemaset'
             def associatedAnchors = createAnchors(numberOfAnchors)
-            mockCpsAnchorService.getAnchors('my-dataspace', 'my-schemaset') >> associatedAnchors
+            mockCpsAnchorService.getAnchorsBySchemaSetName('my-dataspace', 'my-schemaset') >> associatedAnchors
         when: 'schema set deletion is requested with cascade allowed'
             objectUnderTest.deleteSchemaSet('my-dataspace', 'my-schemaset', CASCADE_DELETE_ALLOWED)
         then: 'anchor deletion is called #numberOfAnchors times'
@@ -153,7 +152,7 @@ class CpsModuleServiceImplSpec extends Specification {
 
     def 'Delete schema-set when cascade is prohibited.'() {
         given: 'no anchors are associated with schemaset'
-            mockCpsAnchorService.getAnchors('my-dataspace', 'my-schemaset') >> Collections.emptyList()
+            mockCpsAnchorService.getAnchorsBySchemaSetName('my-dataspace', 'my-schemaset') >> Collections.emptyList()
         when: 'schema set deletion is requested with cascade allowed'
             objectUnderTest.deleteSchemaSet('my-dataspace', 'my-schemaset', CASCADE_DELETE_PROHIBITED)
         then: 'no anchors are deleted'
@@ -170,7 +169,7 @@ class CpsModuleServiceImplSpec extends Specification {
 
     def 'Delete schema-set when cascade is prohibited and schema-set has anchors.'() {
         given: '2 anchors are associated with schemaset'
-            mockCpsAnchorService.getAnchors('my-dataspace', 'my-schemaset') >> createAnchors(2)
+            mockCpsAnchorService.getAnchorsBySchemaSetName('my-dataspace', 'my-schemaset') >> createAnchors(2)
         when: 'schema set deletion is requested with cascade allowed'
             objectUnderTest.deleteSchemaSet('my-dataspace', 'my-schemaset', CASCADE_DELETE_PROHIBITED)
         then: 'Schema-Set in Use exception is thrown'
@@ -179,7 +178,7 @@ class CpsModuleServiceImplSpec extends Specification {
 
     def 'Delete multiple schema-sets when cascade is allowed.'() {
         given: '#numberOfAnchors anchors are associated with each schemaset'
-            mockCpsAnchorService.getAnchors('my-dataspace', ['my-schemaset1', 'my-schemaset2']) >> createAnchors(numberOfAnchors * 2)
+            mockCpsAnchorService.getAnchorsBySchemaSetNames('my-dataspace', ['my-schemaset1', 'my-schemaset2']) >> createAnchors(numberOfAnchors * 2)
         when: 'schema set deletion is requested with cascade allowed'
             objectUnderTest.deleteSchemaSetsWithCascade('my-dataspace', ['my-schemaset1', 'my-schemaset2'])
         then: 'anchor deletion is called #numberOfAnchors times'
@@ -238,6 +237,23 @@ class CpsModuleServiceImplSpec extends Specification {
             1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck)
     }
 
+    def 'Get module references when queried by attributes'() {
+        given: 'a valid dataspace name and anchor name'
+            def dataspaceName = 'someDataspace'
+            def anchorName = 'someAnchor'
+        and: 'a set of parent attributes and child attributes used for filtering'
+            def parentAttributes = ['some-property-key1': 'some-property-val1']
+            def childAttributes = ['some-property-key2': 'some-property-val2']
+        and: 'a list of expected module references returned by the persistence service'
+            def expectedModuleReferences = [new ModuleReference(moduleName: 'some-name', revision: 'some-revision')]
+            mockCpsModulePersistenceService.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes, childAttributes) >> expectedModuleReferences
+        when: 'the method is invoked to retrieve module references by attributes'
+            def actualModuleReferences = objectUnderTest.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes, childAttributes)
+        then: 'the retrieved module references should match the expected module references'
+            assert actualModuleReferences == expectedModuleReferences
+    }
+
+
     def 'Getting module definitions with module name'() {
         given: 'module persistence service returns module definitions for module name'
             def moduleDefinitionsFromPersistenceService = [ new ModuleDefinition('name', 'revision', 'content' ) ]
index 1ad5017..3b10669 100644 (file)
 
 package org.onap.cps.api.impl
 
+import org.onap.cps.impl.utils.CpsValidator
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.PaginationOption
-import org.onap.cps.spi.utils.CpsValidator
 import spock.lang.Specification
 
 class CpsQueryServiceImplSpec extends Specification {
index 57f2f8e..05c8983 100755 (executable)
 \r
 package org.onap.cps.api.impl\r
 \r
+import com.fasterxml.jackson.databind.ObjectMapper\r
 import org.onap.cps.TestUtils\r
 import org.onap.cps.api.CpsAnchorService\r
 import org.onap.cps.api.CpsDeltaService\r
 import org.onap.cps.events.CpsDataUpdateEventsService\r
+import org.onap.cps.impl.utils.CpsValidator\r
 import org.onap.cps.spi.CpsDataPersistenceService\r
 import org.onap.cps.spi.CpsModulePersistenceService\r
 import org.onap.cps.spi.model.Anchor\r
-import org.onap.cps.spi.utils.CpsValidator\r
 import org.onap.cps.utils.ContentType\r
+import org.onap.cps.utils.JsonObjectMapper\r
+import org.onap.cps.utils.PrefixResolver\r
 import org.onap.cps.utils.YangParser\r
 import org.onap.cps.utils.YangParserHelper\r
 import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder\r
@@ -45,15 +48,17 @@ class E2ENetworkSliceSpec extends Specification {
     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)\r
     def mockCpsValidator = Mock(CpsValidator)\r
     def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder()\r
-    def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache)\r
+    def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, timedYangTextSchemaSourceSetBuilder)\r
     def mockCpsDeltaService = Mock(CpsDeltaService)\r
+    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())\r
+    def mockPrefixResolver = Mock(PrefixResolver)\r
 \r
     def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService,\r
             mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)\r
 \r
     def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)\r
-    def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService)\r
-\r
+    def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator,\r
+            yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)\r
     def dataspaceName = 'someDataspace'\r
     def anchorName = 'someAnchor'\r
     def schemaSetName = 'someSchemaSet'\r
index a9f50ee..189e285 100644 (file)
@@ -22,6 +22,7 @@
 package org.onap.cps.api.impl
 
 import org.onap.cps.TestUtils
+import org.onap.cps.impl.utils.CpsValidator
 import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.yang.YangTextSchemaSourceSet
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
@@ -34,8 +35,6 @@ import org.springframework.cache.annotation.EnableCaching
 import org.springframework.cache.caffeine.CaffeineCacheManager
 import org.springframework.test.context.ContextConfiguration
 import spock.lang.Specification
-import org.onap.cps.spi.utils.CpsValidator
-
 
 @SpringBootTest
 @EnableCaching
index e19d120..00b5499 100644 (file)
@@ -24,20 +24,20 @@ import spock.lang.Specification
 
 class DeltaReportBuilderSpec extends Specification{
 
-    def 'Generating delta report with  for add action'() {
+    def 'Generating delta report for "create" action'() {
         when: 'delta report is generated'
             def result = new DeltaReportBuilder()
-                    .actionAdd()
+                    .actionCreate()
                     .withXpath('/xpath')
                     .withTargetData(['data':'leaf-data'])
                     .build()
-        then: 'the delta report contains the "add" action with expected target data'
-            assert result.action == 'add'
+        then: 'the delta report contains the "create" action with expected target data'
+            assert result.action == 'create'
             assert result.xpath == '/xpath'
             assert result.targetData == ['data': 'leaf-data']
     }
 
-    def 'Generating delta report with attributes for remove action'() {
+    def 'Generating delta report with attributes for "remove" action'() {
         when: 'delta report is generated'
             def result = new DeltaReportBuilder()
                     .actionRemove()
index 8cbd493..09d45b9 100644 (file)
@@ -42,7 +42,7 @@ class JsonObjectMapperSpec extends Specification {
             def contentMap = new JsonSlurper().parseText(new String(content))
         and: 'the parsed content is as expected'
             assert contentMap.'test:bookstore'.'bookstore-name' == 'Chapters/Easons'
-        where: 'the following data stores are used'
+        where: 'the following content types are used'
             type << ['String', 'bytes']
     }
 
index 99070fe..18d0502 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.utils
 
+import org.onap.cps.TestUtils
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.Anchor
+import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
 import org.onap.cps.yang.YangTextSchemaSourceSet
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode
 import org.opendaylight.yangtools.yang.model.api.SchemaContext
 import spock.lang.Specification
@@ -32,10 +36,12 @@ class YangParserSpec extends Specification {
 
     def mockYangParserHelper = Mock(YangParserHelper)
     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
+    def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
 
-    def objectUnderTest = new YangParser(mockYangParserHelper, mockYangTextSchemaSourceSetCache)
+    def objectUnderTest = new YangParser(mockYangParserHelper, mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder)
 
     def anchor = new Anchor(dataspaceName: 'my dataspace', schemaSetName: 'my schema')
+    def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
     def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
     def mockSchemaContext = Mock(SchemaContext)
     def containerNodeFromYangUtils = Mock(ContainerNode)
@@ -82,4 +88,15 @@ class YangParserSpec extends Specification {
             1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema')
     }
 
+    def 'Parsing data with yang resource to context map.'() {
+        given: 'the schema source set for the yang resource map is returned'
+            mockTimedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap) >> mockYangTextSchemaSourceSet
+        when: 'parsing some json data'
+            def result = objectUnderTest.parseData(ContentType.JSON, 'some json', yangResourcesNameToContentMap, noParent)
+        then: 'the yang parser helper always returns a container node'
+            1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils
+        and: 'the result is the same container node as return from yang utils'
+            assert result == containerNodeFromYangUtils
+    }
+
 }
diff --git a/csit/install-deps.sh b/csit/install-deps.sh
new file mode 100755 (executable)
index 0000000..ef0b96a
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/bash
+#
+# Copyright 2024 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.
+#
+
+echo "---> install-deps.sh"
+echo "Installing dependencies"
+
+# Create directory for downloaded binaries.
+mkdir -p bin
+touch bin/.gitignore
+
+# Add it to the PATH, so downloaded versions will be used.
+export PATH="$(pwd)/bin:$PATH"
+
+# Download docker-compose.
+if [ ! -x bin/docker-compose ]; then
+  echo " Downloading docker-compose"
+  curl -s -L https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64 > bin/docker-compose
+  chmod +x bin/docker-compose
+else
+  echo " docker-compose already installed"
+fi
+docker-compose version
index e0f21a2..869df22 100644 (file)
@@ -1,5 +1,5 @@
 # ============LICENSE_START=======================================================
-# Modifications Copyright (C) 2022 Nordix Foundation
+# Modifications Copyright (C) 2022-2024 Nordix Foundation
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
 
 services:
   netconf-pnp-simulator:
-    image: nexus3.onap.org:10001/onap/integration/simulators/netconf-pnp-simulator:2.8.6
+    image: blueonap/netconf-pnp-simulator:v2.8.6
     container_name: netconf-simulator
     restart: always
     ports:
@@ -24,3 +24,4 @@ services:
       - "6512:6513"
     volumes:
       - ./netconf-config:/config/modules/stores
+      - ./tls:/config/tls
diff --git a/csit/plans/cps/pnfsim/tls/ca.pem b/csit/plans/cps/pnfsim/tls/ca.pem
new file mode 100644 (file)
index 0000000..4c44738
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID2zCCAsOgAwIBAgIUWDactJMMP2Q2mw0yBnUfQXRsXZMwDQYJKoZIhvcNAQEF
+BQAwfTELMAkGA1UEBhMCSUUxEjAQBgNVBAgMCVdlc3RtZWF0aDEQMA4GA1UEBwwH
+QXRobG9uZTEPMA0GA1UECgwGTm9yZGl4MRMwEQYDVQQDDApleGFtcGxlIENBMSIw
+IAYJKoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTI0MDcyNDExMzMw
+N1oXDTM0MDcyMjExMzMwN1owfTELMAkGA1UEBhMCSUUxEjAQBgNVBAgMCVdlc3Rt
+ZWF0aDEQMA4GA1UEBwwHQXRobG9uZTEPMA0GA1UECgwGTm9yZGl4MRMwEQYDVQQD
+DApleGFtcGxlIENBMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1mNXPz3Vx4l9zhKt7uBm
+8RFebZchO1WjAN5NiIVhVG9Vfktz3DVCbWYpZKwjRrf0g1vBbZk//6qCp6qhHB9m
+4KoDPR1Eu9SX9rri3TD1HWW05HRgxa5j/pk5PCt3/4+eZ31hKcJGsfJ1SwYDk3F/
+bUzgfZ5e4+2LDMgKmKtuhTzQP6ITmqpCN02nEKElDUXgTffo8QBwqnUN91vRmYnC
+9nfD68ipu2Nl19Jam0MRVue2kaZUXF4nisomY4Zmpcf45D6XAdUKMx5wr/kWULIc
+Dz2jE0BkOb/2GCT+sOMnI9riq2X3CoII2wn0NUw0oLYA6lKO5ICZ40w9LfCjeo/r
+yQIDAQABo1MwUTAdBgNVHQ4EFgQUa9fiOtMAq5lM20SZe3jHUnwaQHMwHwYDVR0j
+BBgwFoAUa9fiOtMAq5lM20SZe3jHUnwaQHMwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQUFAAOCAQEADys2rDXMYcjzhMhx0XJtty8STfBsWcsBfcVgwmt1vVwt
+buVn03vCVd90lj+5yqzr9OIntGEt/Mcw4Ca6rxl9bs+XGFxWo0McTxxXEZ5SRFK5
+ISRhWXWfmkxfiZalEymqKT4Xia8+Kydt0jsl93nUNA90GCQki7ngSCkOwoR4yizI
+eT6D/G5oTymEaKt8CuU+eBxQdD1kd6sSeKqXn4WY0dAClPk2VCjMuMYeYB6UWSL3
+HjSaDV4SQnCrvRNQzMJs/zONLPnt05N2GUho30LrXQ0h7zmkYl8AglfEtoCdXnRn
+ikOwkZ/N9V5K8NWJ0yQ5axftH6uxLMQgWIdhL32S0Q==
+-----END CERTIFICATE-----
diff --git a/csit/plans/cps/pnfsim/tls/server_cert.pem b/csit/plans/cps/pnfsim/tls/server_cert.pem
new file mode 100644 (file)
index 0000000..a022dc5
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDijCCAnICFHAVskmbiSw4Q3eiKO6EJw48IS9EMA0GCSqGSIb3DQEBBQUAMH0x
+CzAJBgNVBAYTAklFMRIwEAYDVQQIDAlXZXN0bWVhdGgxEDAOBgNVBAcMB0F0aGxv
+bmUxDzANBgNVBAoMBk5vcmRpeDETMBEGA1UEAwwKZXhhbXBsZSBDQTEiMCAGCSqG
+SIb3DQEJARYTZXhhbXBsZWNhQGxvY2FsaG9zdDAeFw0yNDA3MjQxMTMzMzhaFw0z
+NDA3MjIxMTMzMzhaMIGFMQswCQYDVQQGEwJJRTESMBAGA1UECAwJV2VzdG1lYXRo
+MRAwDgYDVQQHDAdBdGhsb25lMQ8wDQYDVQQKDAZOb3JkaXgxFzAVBgNVBAMMDmV4
+YW1wbGUgc2VydmVyMSYwJAYJKoZIhvcNAQkBFhdleGFtcGxlc2VydmVyQGxvY2Fs
+aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyFCBXEqZ39N7ZZ
+TEU8VJ03bY+kbTCfx9SOL/rP3X9zFOfv0g1TXEx2Yzl/LfRe1N5SgOB24tE34obA
+f++bOGXrsptrZMC5aqlG7cOfjELybUJaUIqMEDX+dte1f7OmPGs0mt2gG4DSU47j
+zGg3KshexLZUGc1fwPnUrBnEPFRCMWIqgSWkC4RrhB9R/uo/eBMh1coH+rSUE/Ba
+vcHlI8orbPu/mupt7tBKapb85nmSglatkZ/YCmfrrm4g5n8jap3e5rO8bs62yYeN
+BF2mHRLOwU+2VmQ1h6L+X0m6hC54UF9WdWyEd02o0HHDr1hDrg3aqrah+dnU+rgM
+hPu8ofkCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAzoHPz0msIst3sT0fQxjqYxYo
+TnU0XzFsMvGC08wbz/iNOS+nvcMuRgG06CUj53BJvdmZPhSfqiYcInM3F4m1MrbM
+dK6L6Vk2eWaL4GwV6B7FR0CWjtTdlETkLSMBNufiqgHebZCT88JDAZAeqhdEbsqk
+7bnZVDdD0qA1Z9ClXFU3jO6n8f5EFn9Ai7FhD7floLHb9M0lheE8xO60RPXNmq/r
+Vf8HrBGHqpiumsMyAuwJONliuSEXNGuB+J+XeQJG91O1oR4Of34HUEZBT/BkoM0X
+iFB+xrLbShsTh1RbAdd1+t76Lsc1lkDVoupaTpdTXA0EmouS9O3CAFWfTjlcGg==
+-----END CERTIFICATE-----
diff --git a/csit/plans/cps/pnfsim/tls/server_key.pem b/csit/plans/cps/pnfsim/tls/server_key.pem
new file mode 100644 (file)
index 0000000..02fd688
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8hQgVxKmd/Te2
+WUxFPFSdN22PpG0wn8fUji/6z91/cxTn79INU1xMdmM5fy30XtTeUoDgduLRN+KG
+wH/vmzhl67Kba2TAuWqpRu3Dn4xC8m1CWlCKjBA1/nbXtX+zpjxrNJrdoBuA0lOO
+48xoNyrIXsS2VBnNX8D51KwZxDxUQjFiKoElpAuEa4QfUf7qP3gTIdXKB/q0lBPw
+Wr3B5SPKK2z7v5rqbe7QSmqW/OZ5koJWrZGf2Apn665uIOZ/I2qd3uazvG7OtsmH
+jQRdph0SzsFPtlZkNYei/l9JuoQueFBfVnVshHdNqNBxw69YQ64N2qq2ofnZ1Pq4
+DIT7vKH5AgMBAAECggEAAJNHWwmmmtzS9rN/EBcHCxPIOc/+pU9XhMaKTvGjc2ge
+gDazJWdDuNgDpYFF2qEPdT47NnQmbQ0Gm/KqcUi/+0+k0+SYAh6OvMWCpD4wZ2Pm
+AXXVGRckVYXZRv8+zIWNWaZncpWyf8okhyMa1JaWgtYHM6c+DOpl5F1JySpYJMmt
+laWH0fMCYdYM5N8RisXfImmf+bBcehIZFvq47f5LefvPBHCss/L+Nym/ypMl+qo1
+MvVPkMNIhJFb/NQSYknp5ino2uo06RNOhftu/ig8CgzuSYvK18Ia9NEAKd2kS/y7
+MtYipgBCQqax5ulYmDAmnSrm+KpNhfk+CcMBlW9yoQKBgQDboMRpUMyinK9jrY7R
+GqISWcrDRFEwKfPr0rFtBZM/0ODjZBXMDSej28LwCo+6Vo8dF4G9fValuyyrNMQl
+T0OpcDxBKV3yCHZoXmzTKx/vcF56hOgkpwT8gHWpVsqVxcjIyERZrynblMnZixcv
+ppF33YJv8A7oParHGnj9zqWvVwKBgQDbvWlV7/CV5y5kYnNKLIZ51xaTZpAt1CE7
+N4B4x41y+jTGtscQoDlIMgOydC8F+dBeolvMXEyN002pYj8K9yxQcHCz2F62A2na
+ZA+Vj6xTq2/YGhBBrJ21eaEOcKBc9rrP0s2lzhzVb/fbPq0hkgWqQuJKsHaq2y9O
+fYUBfbB3LwKBgEexPgwmzPXT8ci28eS+LeORngeJuHrhZvc26qXs6Pku5Qo1NIxM
+SwFJDmQu/mXUNZlIgBhr3qnw5I7qhZCsRCj+Mx0ONNV5/7ToBdwUurL9WkniMqks
+QAtwn3fsleq4CmfIP8+Kxz4fXph9t87dL6USEK8bjLIw1xtxP8eR+jG3AoGAcuM4
+ZLcbqbSCW/fhYWGgOanMYurX7S4g5c4h/IQRH5FT8KV1tOqgqG+F4VK/lzdCy4fF
+yTZkzC4zR6FXZstOvwva0R0Kf82PFaEFSOQibGiRBIK0BzJSDqT2IQ+fuJtDlw8X
+eF4oUyvEgjvl10x6a8emeviCQthwhnA4D0yA6/8CgYEA14WLsOllb6IjO3c+mwFp
+Gs8pDrB/XPH7bPH1fVO+60OMT5OMTlEa/cVlhbNWuHVR7+yVQCh7HuzVPBtSdyNW
+4+8UuAz3eLm93he6DiH7D4U7Zx2TKB2B6PBbHz9aEh96l67TfAHy+u3x0mVFziZ7
+HNe49uMd7A5r4QgflshgDgs=
+-----END PRIVATE KEY-----
index 48b4d90..b2dec5c 100644 (file)
Binary files a/csit/plans/cps/sdnc/certs/keys0.zip and b/csit/plans/cps/sdnc/certs/keys0.zip differ
index 80829eb..036f14b 100755 (executable)
@@ -1,7 +1,6 @@
 #!/bin/bash
 #
 # Copyright 2016-2017 Huawei Technologies Co., Ltd.
-# Modifications 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.
@@ -19,7 +18,7 @@
 # Modifications copyright (c) 2020-2021 Samsung Electronics Co., Ltd.
 # Modifications Copyright (C) 2021 Pantheon.tech
 # Modifications Copyright (C) 2021 Bell Canada.
-# Modifications Copyright (C) 2021 Nordix Foundation.
+# Modifications Copyright (C) 2021-2024 Nordix Foundation.
 #
 # Branched from ccsdk/distribution to this repository Feb 23, 2021
 #
@@ -59,10 +58,6 @@ export $(cut -d= -f1 $WORKSPACE/plans/cps/test.properties)
 ###################### setup cps-ncmp ############################
 cd $CPS_HOME/docker-compose
 
-curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > docker-compose
-chmod +x docker-compose
-docker-compose version
-
 # start CPS/NCMP, DMI Plugin, and PostgreSQL containers with docker compose
 docker-compose --profile dmi-service up -d
 
@@ -82,4 +77,4 @@ check_health $DMI_HOST:$DMI_PORT 'dmi-plugin'
 
 ###################### ROBOT Configurations ##########################
 # Pass variables required for Robot test suites in ROBOT_VARIABLES
-ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v DMI_CSIT_STUB_HOST:$LOCAL_IP -v DMI_CSIT_STUB_PORT:$DMI_DEMO_STUB_PORT -v DMI_AUTH_ENABLED:$DMI_AUTH_ENABLED -v DATADIR_CPS_CORE:$WORKSPACE/data/cps-core -v DATADIR_NCMP:$WORKSPACE/data/ncmp -v DATADIR_SUBS_NOTIFICATION:$WORKSPACE/data/subscription-notification --exitonfailure"
\ No newline at end of file
+ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v DMI_CSIT_STUB_HOST:$LOCAL_IP -v DMI_CSIT_STUB_PORT:$DMI_DEMO_STUB_PORT -v DMI_AUTH_ENABLED:$DMI_AUTH_ENABLED -v DATADIR_CPS_CORE:$WORKSPACE/data/cps-core -v DATADIR_NCMP:$WORKSPACE/data/ncmp -v DATADIR_SUBS_NOTIFICATION:$WORKSPACE/data/subscription-notification --exitonfailure"
index 7beb907..e2ecd88 100755 (executable)
 # Modifications copyright (c) 2017 AT&T Intellectual Property
 # Modifications copyright (c) 2020 Samsung Electronics Co., Ltd.
 # Modifications Copyright (C) 2021 Pantheon.tech
-# Modifications Copyright (C) 2021 Nordix Foundation
+# Modifications Copyright (C) 2021-2024 Nordix Foundation
 # Branched from ccsdk/distribution to this repository Feb 23, 2021
 #
 echo '================================== docker info =========================='
 docker ps -a
 
 echo '================================== CPS-NCMP Logs ========================'
-docker logs cps-and-ncmp
+for CONTAINER_ID in $(docker ps --filter "name=cps-and-ncmp" --format "{{.ID}}"); do
+    echo "CPS-NCMP Logs for container: $CONTAINER_ID"
+    docker logs "$CONTAINER_ID"
+done
 
 echo '================================== DMI Logs ============================='
 docker logs ncmp-dmi-plugin
 
+echo '================================== DMI Stub Logs ========================'
+docker logs ncmp-dmi-plugin-demo-and-csit-stub
+
 echo '================================== SDNC Logs ============================'
 docker logs sdnc
 
index 15485b7..396bdd3 100644 (file)
@@ -21,7 +21,7 @@ DMI_SERVICE_URL=http://$LOCAL_IP:$DMI_PORT
 DOCKER_REPO=nexus3.onap.org:10003
 
 CPS_VERSION=latest
-DMI_VERSION=1.5.0-SNAPSHOT-latest
+DMI_VERSION=1.6.0-SNAPSHOT-latest
 
 ADVISED_MODULES_SYNC_SLEEP_TIME_MS=2000
 CMHANDLE_DATA_SYNC_SLEEP_TIME_MS=2000
index e80f0d9..24c7745 100644 (file)
@@ -21,4 +21,4 @@ cps-data-sync
 ncmp-passthrough
 cm-handle-query
 cps-trust-level
-cps-data-operations
\ No newline at end of file
+cps-data-operations
index fbd5dc5..1b8578e 100755 (executable)
@@ -71,7 +71,7 @@ echo "Versioning information:"
 python3 --version
 
 echo "Installing confluent kafka library for robot framework:"
-pip install robotframework-confluentkafkalibrary
+pip install robotframework-confluentkafkalibrary==2.4.0-2
 
 pip freeze
 python3 -m robot.run --version || :
\ No newline at end of file
index 32bfa6f..3eeb1ab 100644 (file)
@@ -9,7 +9,7 @@ robotframework-requests==0.9.3
 robotframework-selenium2library==3.0.0
 robotframework-extendedselenium2library
 robotframework-sshlibrary
-robotframework-confluentkafkalibrary
+robotframework-confluentkafkalibrary==2.4.0-2
 scapy
 # Module jsonpath is needed by current AAA idmlite suite.
 jsonpath-rw
index 0981fc6..93941e2 100755 (executable)
@@ -2,6 +2,7 @@
 #
 # Copyright 2016-2017 Huawei Technologies Co., Ltd.
 # Modification Copyright 2019-2021 Â© Samsung Electronics Co., Ltd.
+# Modification Copyright (C) 2024 Nordix Foundation.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
index fcb3c92..f362cc7 100755 (executable)
@@ -2,6 +2,7 @@
 #
 # Copyright 2020-2021 Â© Samsung Electronics Co., Ltd.
 # Modifications Copyright (C) 2021 Pantheon.tech
+# Modifications Copyright (C) 2024 Nordix Foundation.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -28,6 +29,8 @@ rm -rf ${WORKSPACE}/archives
 mkdir -p ${WORKSPACE}/archives
 cd ${WORKSPACE}
 
+source install-deps.sh
+
 # Execute all test-suites defined under plans subdirectory
 for dir in plans/*/
 do
index 17dce16..96212ff 100644 (file)
@@ -26,6 +26,7 @@ Library               OperatingSystem
 Library               RequestsLibrary
 Library               BuiltIn
 Library               ConfluentKafkaLibrary
+Library               String
 
 Suite Setup           Create Session      CPS_URL    http://${CPS_CORE_HOST}:${CPS_CORE_PORT}
 
@@ -48,7 +49,7 @@ NCMP Data Operation, forwarded to DMI, response on Client Topic
                                          POST On Session     CPS_URL   ncmpInventory/v1/ch         headers=${headers}     data=${newCmHandleRequestBody}
         ${getCmHandleUri}=               Set Variable        ${ncmpBasePath}/v1/ch/CMHandle1
         ${getCmHandleHeaders}=           Create Dictionary   Authorization=${auth}
-        Wait Until Keyword Succeeds      8sec    100ms       Is CM Handle READY    ${getCmHandleUri}    ${getCmHandleHeaders}    CMHandle1
+        Wait Until Keyword Succeeds      20sec    200ms       Is CM Handle READY    ${getCmHandleUri}    ${getCmHandleHeaders}    CMHandle1
         ${response}=                     POST On Session     CPS_URL   ${uri}   params=${params}   headers=${headers}     data=${dataOperationReqBody}
         Set Global Variable              ${expectedRequestId}       ${response.json()}[requestId]
         Should Be Equal As Strings       ${response.status_code}   200
@@ -63,7 +64,7 @@ Consume cloud event from client topic
         Compare Header Values       ${header_key_value_pair[0]}   ${header_key_value_pair[1]}      "ce_specversion"      "1.0"
         Compare Header Values       ${header_key_value_pair[0]}   ${header_key_value_pair[1]}      "ce_type"             "org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent"
         Compare Header Values       ${header_key_value_pair[0]}   ${header_key_value_pair[1]}      "ce_correlationid"    "${expectedRequestId}"
-                Compare Header Values       ${header_key_value_pair[0]}   ${header_key_value_pair[1]}      "ce_source"           "DMI"
+        Compare Header Values       ${header_key_value_pair[0]}   ${header_key_value_pair[1]}      "ce_source"           "DMI"
     END
     [Teardown]                      Basic Teardown                    ${group_id}
 
@@ -78,12 +79,20 @@ Is CM Handle READY
     [Arguments]    ${uri}    ${headers}    ${cmHandle}
     ${response}=    GET On Session    CPS_URL    ${uri}    headers=${headers}
     Should Be Equal As Strings    ${response.status_code}    200
+    ${number_of_items}=    Count Items In JSON Response    ${response}
+    Should Be True    ${number_of_items} > 0
     FOR  ${item}  IN  ${response.json()}
             IF  "${item['cmHandle']}" == "${cmHandle}"
                 Should Be Equal As Strings    ${item['state']['cmHandleState']}    READY
             END
     END
 
+Count Items In JSON Response
+    [Arguments]    ${response}
+    ${json_data}=    Evaluate    json.loads('${response.content.decode("utf-8")}')   json
+    ${number_of_items}=    Get Length    ${json_data}
+    RETURN    ${number_of_items}
+
 Basic Teardown
     [Arguments]     ${group_id}
     Unsubscribe     ${group_id}
index c0ee4da..b8ba479 100644 (file)
@@ -35,11 +35,6 @@ ${ncmpBasePath}           /ncmp
 
 *** Test Cases ***
 
-Check if ietfYang-PNFDemo is READY
-    ${uri}=        Set Variable       ${ncmpBasePath}/v1/ch/ietfYang-PNFDemo
-    ${headers}=    Create Dictionary  Authorization=${auth}
-    Wait Until Keyword Succeeds       10sec    100ms    Is CM Handle READY    ${uri}    ${headers}    ietfYang-PNFDemo
-
 Operational state goes to UNSYNCHRONIZED when data sync (flag) is enabled
     ${uri}=              Set Variable       ${ncmpBasePath}/v1/ch/ietfYang-PNFDemo/data-sync
     ${params}=           Create Dictionary  dataSyncEnabled=true
@@ -54,18 +49,9 @@ Operational state goes to UNSYNCHRONIZED when data sync (flag) is enabled
 Operational state goes to SYNCHRONIZED after sometime when data sync (flag) is enabled
     ${uri}=        Set Variable       ${ncmpBasePath}/v1/ch/ietfYang-PNFDemo/state
     ${headers}=    Create Dictionary  Authorization=${auth}
-    Wait Until Keyword Succeeds    10sec    100ms    Is CM Handle State SYNCHRONIZED    ${uri}    ${headers}
+    Wait Until Keyword Succeeds    40sec    100ms    Is CM Handle State SYNCHRONIZED    ${uri}    ${headers}
 
 *** Keywords ***
-Is CM Handle READY
-    [Arguments]    ${uri}    ${headers}    ${cmHandle}
-    ${response}=    GET On Session    CPS_URL    ${uri}    headers=${headers}
-    Should Be Equal As Strings    ${response.status_code}    200
-    FOR  ${item}  IN  ${response.json()}
-            IF  "${item['cmHandle']}" == "${cmHandle}"
-                Should Be Equal As Strings    ${item['state']['cmHandleState']}    READY
-            END
-    END
 
 Is CM Handle State SYNCHRONIZED
     [Arguments]    ${uri}    ${headers}
index bb881f6..514076f 100644 (file)
@@ -25,6 +25,7 @@ Library               Collections
 Library               OperatingSystem
 Library               RequestsLibrary
 Library               BuiltIn
+Library               String
 
 Suite Setup           Create Session      CPS_URL    http://${CPS_CORE_HOST}:${CPS_CORE_PORT}
 
@@ -88,13 +89,40 @@ Get cm handle details and confirm it has been deleted
     ${headers}=          Create Dictionary  Authorization=${auth}
     ${response}=         GET On Session     CPS_URL   ${uri}   headers=${headers}   expected_status=404
 
+Check if ietfYang-PNFDemo is READY
+    ${uri}=        Set Variable       ${ncmpBasePath}/v1/ch/ietfYang-PNFDemo
+    ${headers}=    Create Dictionary  Authorization=${auth}
+    Wait Until Keyword Succeeds       20sec    200ms    Is CM Handle READY    ${uri}    ${headers}    ietfYang-PNFDemo
+
 Get modules for registered data node
     ${uri}=              Set Variable       ${ncmpBasePath}/v1/ch/ietfYang-PNFDemo/modules
     ${headers}=          Create Dictionary  Authorization=${auth}
     ${response}=         GET On Session     CPS_URL   ${uri}   headers=${headers}
     Should Be Equal As Strings              ${response.status_code}   200
+    ${number_of_items}=    Count Items In JSON Response    ${response}
+    Should Be True    ${number_of_items} > 0
     FOR   ${item}   IN  @{response.json()}
             IF   "${item['moduleName']}" == "stores"
                 Should Be Equal As Strings              "${item['revision']}"   "2020-09-15"
             END
-    END
\ No newline at end of file
+    END
+
+*** Keywords ***
+
+Is CM Handle READY
+    [Arguments]    ${uri}    ${headers}    ${cmHandle}
+    ${response}=    GET On Session    CPS_URL    ${uri}    headers=${headers}
+    Should Be Equal As Strings    ${response.status_code}    200
+    ${number_of_items}=    Count Items In JSON Response    ${response}
+    Should Be True    ${number_of_items} > 0
+    FOR  ${item}  IN  ${response.json()}
+            IF  "${item['cmHandle']}" == "${cmHandle}"
+                Should Be Equal As Strings    ${item['state']['cmHandleState']}    READY
+            END
+    END
+
+Count Items In JSON Response
+    [Arguments]    ${response}
+    ${json_data}=    Evaluate    json.loads('${response.content.decode("utf-8")}')   json
+    ${number_of_items}=    Get Length    ${json_data}
+    RETURN    ${number_of_items}
\ No newline at end of file
index e4deeff..810bcf4 100644 (file)
@@ -36,7 +36,7 @@ ${ncmpBasePath}                             /ncmp/v1
 ${dmiUrl}                                   http://${DMI_HOST}:${DMI_PORT}
 ${jsonCreateCmHandles}                      {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","createdCmHandles":[{"trustLevel":"COMPLETE","cmHandle":"CH-1"},{"trustLevel":"COMPLETE","cmHandle":"CH-2"},{"cmHandle":"CH-3"},{"trustLevel":"NONE","cmHandle":"CH-4"}]}
 ${jsonTrustLevelPropertyQueryParameters}    {"cmHandleQueryParameters": [{"conditionName": "cmHandleWithTrustLevel", "conditionParameters": [ {"trustLevel": "COMPLETE"} ] }]}
-${jsonTrustLevelQueryResponse}              {"data":{"attributeValueChange":[{"attributeName":"trustLevel","newAttributeValue":"NONE"}]}}
+${jsonTrustLevelEventPayload}               {"data":{"attributeValueChange":[{"attributeName":"trustLevel","newAttributeValue":"NONE"}]}}
 
 *** Test Cases ***
 Register data node
@@ -57,7 +57,7 @@ Verify notification
         Compare Header Values       ${header_key_value_pair[0]}   ${header_key_value_pair[1]}     "ce_type"             "org.onap.cps.ncmp.events.avc.ncmp_to_client.AvcEvent"
         Compare Header Values       ${header_key_value_pair[0]}   ${header_key_value_pair[1]}     "ce_correlationid"    "CH-4"
     END
-    Should Be Equal As Strings      ${payload}    ${jsonTrustLevelQueryResponse}
+    Should Be Equal As Strings      ${payload}    ${jsonTrustLevelEventPayload}
     [Teardown]    Basic Teardown    ${group_id}
 
 Retrieve CM Handle ids where query parameters Match (trust level query)
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml
deleted file mode 100644 (file)
index e62c01d..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ============LICENSE_START=======================================================
-  Copyright (C) 2023-2024 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=========================================================
--->
-
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.onap.cps</groupId>
-        <artifactId>dmi-plugin-demo-and-csit-stub</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
-    </parent>
-
-    <artifactId>dmi-plugin-demo-and-csit-stub-app</artifactId>
-
-    <properties>
-        <app>org.onap.cps.ncmp.dmi.rest.stub.DmiDemoApplication</app>
-        <maven.build.timestamp.format>yyyyMMdd'T'HHmmss'Z'</maven.build.timestamp.format>
-        <base.image>${docker.pull.registry}/onap/integration-java17:12.0.0</base.image>
-        <image.tag>${project.version}-${maven.build.timestamp}</image.tag>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    </properties>
-
-    <build>
-        <pluginManagement>
-            <plugins>
-                <plugin>
-                    <groupId>com.google.cloud.tools</groupId>
-                    <artifactId>jib-maven-plugin</artifactId>
-                    <configuration>
-                        <container>
-                            <mainClass>${app}</mainClass>
-                            <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
-                        </container>
-                        <from>
-                            <image>${base.image}</image>
-                        </from>
-                        <to>
-                            <tags>
-                                <tag>latest</tag>
-                            </tags>
-                            <image>${docker.push.registry}/onap/${image.name}:${image.tag}</image>
-                        </to>
-                    </configuration>
-                    <executions>
-                        <execution>
-                            <phase>package</phase>
-                            <id>build</id>
-                            <goals>
-                                <goal>dockerBuild</goal>
-                            </goals>
-                        </execution>
-                        <execution>
-                            <phase>deploy</phase>
-                            <id>buildAndPush</id>
-                            <goals>
-                                <goal>build</goal>
-                            </goals>
-                        </execution>
-                    </executions>
-                </plugin>
-            </plugins>
-        </pluginManagement>
-    </build>
-
-    <profiles>
-        <profile>
-            <id>docker</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <properties>
-                <image.name>dmi-plugin-demo-and-csit-stub</image.name>
-            </properties>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>com.google.cloud.tools</groupId>
-                        <artifactId>jib-maven-plugin</artifactId>
-                        <version>3.3.2</version>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
-    <dependencies>
-        <dependency>
-            <groupId>org.onap.cps</groupId>
-            <artifactId>dmi-plugin-demo-and-csit-stub-service</artifactId>
-            <version>${project.version}</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>junit</groupId>
-                    <artifactId>junit</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-    </dependencies>
-</project>
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml
deleted file mode 100644 (file)
index c0b4ead..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<!--
-  ============LICENSE_START=======================================================
-  Copyright (C) 2023-2024 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=========================================================
--->
-
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.onap.cps</groupId>
-        <artifactId>dmi-plugin-demo-and-csit-stub</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
-    </parent>
-    <artifactId>dmi-plugin-demo-and-csit-stub-service</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-tomcat</artifactId>
-                </exclusion>
-            </exclusions>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-actuator</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.googlecode.json-simple</groupId>
-            <artifactId>json-simple</artifactId>
-            <version>1.1.1</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>junit</groupId>
-                    <artifactId>junit</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-
-        <dependency>
-            <groupId>org.onap.cps</groupId>
-            <artifactId>cps-ncmp-rest</artifactId>
-        </dependency>
-    </dependencies>
-</project>
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java
deleted file mode 100644 (file)
index f154be6..0000000
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 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.dmi.rest.stub.controller;
-
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUCCESS;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.cloudevents.CloudEvent;
-import io.cloudevents.core.builder.CloudEventBuilder;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
-import org.onap.cps.ncmp.api.impl.utils.EventDateTimeFormatter;
-import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.CmHandle;
-import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DataOperationRequest;
-import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DmiDataOperationRequest;
-import org.onap.cps.ncmp.dmi.rest.stub.utils.ResourceFileReaderUtil;
-import org.onap.cps.ncmp.events.async1_0_0.Data;
-import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent;
-import org.onap.cps.ncmp.events.async1_0_0.Response;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.ResourceLoader;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("${rest.api.dmi-stub-base-path}")
-@RequiredArgsConstructor
-@Slf4j
-public class DmiRestStubController {
-
-    private static final String DEFAULT_TAG = "tagD";
-    private static final String dataOperationEventType = "org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent";
-    private static final Map<String, String> moduleSetTagPerCmHandleId = new HashMap<>();
-    private final KafkaTemplate<String, CloudEvent> cloudEventKafkaTemplate;
-    private final ObjectMapper objectMapper;
-    private final ApplicationContext applicationContext;
-    @Value("${app.ncmp.async-m2m.topic}")
-    private String ncmpAsyncM2mTopic;
-    @Value("${delay.module-references-delay-ms}")
-    private long moduleReferencesDelayMs;
-    @Value("${delay.module-resources-delay-ms}")
-    private long moduleResourcesDelayMs;
-    @Value("${delay.data-for-cm-handle-delay-ms}")
-    private long dataForCmHandleDelayMs;
-
-    /**
-     * This code defines a REST API endpoint for adding new the module set tag mapping. The endpoint receives the
-     * cmHandleId and moduleSetTag as request body and add into moduleSetTagPerCmHandleId map with the provided
-     * values.
-     *
-     * @param requestBody map of cmHandleId and moduleSetTag
-     * @return a ResponseEntity object containing the updated moduleSetTagPerCmHandleId map as the response body
-     */
-    @PostMapping("/v1/tagMapping")
-    public ResponseEntity<Map<String, String>> addTagForMapping(@RequestBody final Map<String, String> requestBody) {
-        moduleSetTagPerCmHandleId.putAll(requestBody);
-        return new ResponseEntity<>(requestBody, HttpStatus.CREATED);
-    }
-
-    /**
-     * This code defines a GET endpoint of  module set tag mapping.
-     *
-     * @return The map represents the module set tag mapping.
-     */
-    @GetMapping("/v1/tagMapping")
-    public ResponseEntity<Map<String, String>> getTagMapping() {
-        return ResponseEntity.ok(moduleSetTagPerCmHandleId);
-    }
-
-    /**
-     * This code defines a GET endpoint of  module set tag by cm handle ID.
-     *
-     * @return The map represents the module set tag mapping filtered by cm handle ID.
-     */
-    @GetMapping("/v1/tagMapping/ch/{cmHandleId}")
-    public ResponseEntity<String> getTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
-        return ResponseEntity.ok(moduleSetTagPerCmHandleId.get(cmHandleId));
-    }
-
-    /**
-     * This code defines a REST API endpoint for updating the module set tag mapping. The endpoint receives the
-     * cmHandleId and moduleSetTag as request body and updates the moduleSetTagPerCmHandleId map with the provided
-     * values.
-     *
-     * @param requestBody map of cmHandleId and moduleSetTag
-     * @return a ResponseEntity object containing the updated moduleSetTagPerCmHandleId map as the response body
-     */
-
-    @PutMapping("/v1/tagMapping")
-    public ResponseEntity<Map<String, String>> updateTagMapping(@RequestBody final Map<String, String> requestBody) {
-        moduleSetTagPerCmHandleId.putAll(requestBody);
-        return ResponseEntity.noContent().build();
-    }
-
-    /**
-     * It contains a method to delete an entry from the moduleSetTagPerCmHandleId map.
-     * The method takes a cmHandleId as a parameter and removes the corresponding entry from the map.
-     *
-     * @return a ResponseEntity containing the updated map.
-     */
-    @DeleteMapping("/v1/tagMapping/ch/{cmHandleId}")
-    public ResponseEntity<String> deleteTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
-        moduleSetTagPerCmHandleId.remove(cmHandleId);
-        return ResponseEntity.ok(String.format("Mapping of %s is deleted successfully", cmHandleId));
-    }
-
-    /**
-     * Get all modules for given cm handle.
-     *
-     * @param cmHandleId              The identifier for a network function, network element, subnetwork,
-     *                                or any other cm object by managed Network CM Proxy
-     * @param moduleReferencesRequest module references request body
-     * @return ResponseEntity response entity having module response as json string.
-     */
-    @PostMapping("/v1/ch/{cmHandleId}/modules")
-    public ResponseEntity<String> getModuleReferences(@PathVariable("cmHandleId") final String cmHandleId,
-                                                      @RequestBody final Object moduleReferencesRequest) {
-        delay(moduleReferencesDelayMs);
-        try {
-            log.info("Incoming DMI request body: {}",
-                    objectMapper.writeValueAsString(moduleReferencesRequest));
-        } catch (final JsonProcessingException jsonProcessingException) {
-            log.info("Unable to parse dmi data operation request to json string");
-        }
-        final String moduleResponseContent = getModuleResourceResponse(cmHandleId,
-                "ModuleResponse.json");
-        log.info("cm handle: {} requested for modules", cmHandleId);
-        return ResponseEntity.ok(moduleResponseContent);
-    }
-
-    /**
-     * Retrieves module resources for a given cmHandleId.
-     *
-     * @param cmHandleId                 The identifier for a network function, network element, subnetwork,
-     *                                   or any other cm object by managed Network CM Proxy
-     * @param moduleResourcesReadRequest module resources read request body
-     * @return ResponseEntity response entity having module resources response as json string.
-     */
-    @PostMapping("/v1/ch/{cmHandleId}/moduleResources")
-    public ResponseEntity<String> retrieveModuleResources(
-            @PathVariable("cmHandleId") final String cmHandleId,
-            @RequestBody final Object moduleResourcesReadRequest) {
-        delay(moduleResourcesDelayMs);
-        final String moduleResourcesResponseContent = getModuleResourceResponse(cmHandleId,
-                "ModuleResourcesResponse.json");
-        log.info("cm handle: {} requested for modules resources", cmHandleId);
-        return ResponseEntity.ok(moduleResourcesResponseContent);
-    }
-
-    /**
-     * Create resource data from passthrough operational or running for a cm handle.
-     *
-     * @param cmHandleId              The identifier for a network function, network element, subnetwork,
-     *                                or any other cm object by managed Network CM Proxy
-     * @param datastoreName           datastore name
-     * @param resourceIdentifier      resource identifier
-     * @param options                 options
-     * @param topic                   client given topic name
-     * @return (@ code ResponseEntity) response entity
-     */
-    @PostMapping("/v1/ch/{cmHandleId}/data/ds/{datastoreName}")
-    public ResponseEntity<String> getResourceDataForCmHandle(
-            @PathVariable("cmHandleId") final String cmHandleId,
-            @PathVariable("datastoreName") final String datastoreName,
-            @RequestParam(value = "resourceIdentifier") final String resourceIdentifier,
-            @RequestParam(value = "options", required = false) final String options,
-            @RequestParam(value = "topic", required = false) final String topic,
-            @RequestHeader(value = "Authorization", required = false) final String authorization) {
-        log.info("DMI AUTH HEADER: {}", authorization);
-        delay(dataForCmHandleDelayMs);
-        final String sampleJson = ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
-                ResourceLoader.CLASSPATH_URL_PREFIX + "data/operational/ietf-network-topology-sample-rfc8345.json"));
-        return ResponseEntity.ok(sampleJson);
-    }
-
-    /**
-     * This method is not implemented for ONAP DMI plugin.
-     *
-     * @param topic                   client given topic name
-     * @param requestId               requestId generated by NCMP as an ack for client
-     * @param dmiDataOperationRequest list of operation details
-     * @return (@ code ResponseEntity) response entity
-     */
-    @PostMapping("/v1/data")
-    public ResponseEntity<Void> getResourceDataForCmHandleDataOperation(
-            @RequestParam(value = "topic") final String topic,
-            @RequestParam(value = "requestId") final String requestId,
-            @RequestBody final DmiDataOperationRequest dmiDataOperationRequest) {
-        delay(dataForCmHandleDelayMs);
-        try {
-            log.info("Request received from the NCMP to DMI Plugin: {}",
-                    objectMapper.writeValueAsString(dmiDataOperationRequest));
-        } catch (final JsonProcessingException jsonProcessingException) {
-            log.info("Unable to process dmi data operation request to json string");
-        }
-        dmiDataOperationRequest.getOperations().forEach(dmiDataOperation -> {
-            final DataOperationEvent dataOperationEvent = getDataOperationEvent(dmiDataOperation);
-            dmiDataOperation.getCmHandles().forEach(cmHandle -> {
-                dataOperationEvent.getData().getResponses().get(0).setIds(List.of(cmHandle.getId()));
-                final CloudEvent cloudEvent = buildAndGetCloudEvent(topic, requestId, dataOperationEvent);
-                cloudEventKafkaTemplate.send(ncmpAsyncM2mTopic, UUID.randomUUID().toString(), cloudEvent);
-            });
-        });
-        return new ResponseEntity<>(HttpStatus.ACCEPTED);
-    }
-
-    private CloudEvent buildAndGetCloudEvent(final String topic, final String requestId,
-                                             final DataOperationEvent dataOperationEvent) {
-        CloudEvent cloudEvent = null;
-        try {
-            cloudEvent = CloudEventBuilder.v1()
-                    .withId(UUID.randomUUID().toString())
-                    .withSource(URI.create("DMI"))
-                    .withType(dataOperationEventType)
-                    .withDataSchema(URI.create("urn:cps:" + dataOperationEventType + ":1.0.0"))
-                    .withTime(EventDateTimeFormatter.toIsoOffsetDateTime(
-                            EventDateTimeFormatter.getCurrentIsoFormattedDateTime()))
-                    .withData(objectMapper.writeValueAsBytes(dataOperationEvent))
-                    .withExtension("destination", topic)
-                    .withExtension("correlationid", requestId)
-                    .build();
-        } catch (final JsonProcessingException jsonProcessingException) {
-            log.error("Unable to parse event into bytes. cause : {}", jsonProcessingException.getMessage());
-        }
-        return cloudEvent;
-    }
-
-    private DataOperationEvent getDataOperationEvent(final DataOperationRequest dataOperationRequest) {
-        final Response response = new Response();
-        response.setOperationId(dataOperationRequest.getOperationId());
-        response.setStatusCode(SUCCESS.getCode());
-        response.setStatusMessage(SUCCESS.getMessage());
-        response.setIds(dataOperationRequest.getCmHandles().stream().map(CmHandle::getId).toList());
-        response.setResourceIdentifier(dataOperationRequest.getResourceIdentifier());
-        response.setOptions(dataOperationRequest.getOptions());
-        final String ietfNetworkTopologySample = ResourceFileReaderUtil
-                .getResourceFileContent(applicationContext.getResource(
-                        ResourceLoader.CLASSPATH_URL_PREFIX
-                                + "data/operational/ietf-network-topology-sample-rfc8345.json"));
-        final JSONParser jsonParser = new JSONParser();
-        try {
-            response.setResult(jsonParser.parse(ietfNetworkTopologySample));
-        } catch (final ParseException parseException) {
-            log.error("Unable to parse event result as json object. cause : {}", parseException.getMessage());
-        }
-        final List<Response> responseList = new ArrayList<>(1);
-        responseList.add(response);
-        final Data data = new Data();
-        data.setResponses(responseList);
-        final DataOperationEvent dataOperationEvent = new DataOperationEvent();
-        dataOperationEvent.setData(data);
-        return dataOperationEvent;
-    }
-
-    private String getModuleResourceResponse(final String cmHandleId, final String moduleResponseType) {
-        if (moduleSetTagPerCmHandleId.isEmpty()) {
-            log.info("Using default module responses of type ietfYang");
-            return ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
-                    ResourceLoader.CLASSPATH_URL_PREFIX
-                            + String.format("module/ietfYang-%s", moduleResponseType)));
-        }
-        final String moduleSetTag = moduleSetTagPerCmHandleId.getOrDefault(cmHandleId, DEFAULT_TAG);
-        final String moduleResponseFilePath = String.format("module/%s-%s", moduleSetTag, moduleResponseType);
-        final Resource moduleResponseResource = applicationContext.getResource(
-                ResourceLoader.CLASSPATH_URL_PREFIX + moduleResponseFilePath);
-        log.info("Using module responses from : {}", moduleResponseFilePath);
-        return ResourceFileReaderUtil.getResourceFileContent(moduleResponseResource);
-    }
-
-    private void delay(final long milliseconds) {
-        try {
-            Thread.sleep(milliseconds);
-        } catch (final InterruptedException e) {
-            log.error("Thread sleep interrupted: {}", e.getMessage());
-            Thread.currentThread().interrupt();
-        }
-    }
-}
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/CmHandle.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/CmHandle.java
deleted file mode 100644 (file)
index 93a90c9..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.dmi.rest.stub.model.data.operational;
-
-import java.util.HashMap;
-import java.util.Map;
-import lombok.Getter;
-import lombok.Setter;
-
-@Setter
-@Getter
-public class CmHandle {
-    private String id;
-    private Map<String, String> cmHandleProperties = new HashMap<>();
-}
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java
deleted file mode 100644 (file)
index 85c649e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.dmi.rest.stub.model.data.operational;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.Getter;
-import lombok.Setter;
-
-@JsonInclude(JsonInclude.Include.NON_NULL)
-@Setter
-@Getter
-public class DataOperationRequest {
-    private String operation;
-    private String operationId;
-    private String datastore;
-    private String options;
-    private String resourceIdentifier;
-    private List<CmHandle> cmHandles = new ArrayList<>();
-}
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DmiDataOperationRequest.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DmiDataOperationRequest.java
deleted file mode 100644 (file)
index 0771e77..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.dmi.rest.stub.model.data.operational;
-
-import java.util.List;
-import lombok.Getter;
-import lombok.Setter;
-
-@Setter
-@Getter
-public class DmiDataOperationRequest {
-
-    private List<DataOperationRequest> operations;
-}
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/utils/ResourceFileReaderUtil.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/utils/ResourceFileReaderUtil.java
deleted file mode 100644 (file)
index 0d2adee..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.dmi.rest.stub.utils;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.core.io.Resource;
-import org.springframework.util.StreamUtils;
-
-/**
- * Common convenience methods for reading resource file content.
- */
-@Slf4j
-public class ResourceFileReaderUtil {
-
-    /**
-     * Converts a resource file content into string.
-     *
-     * @param fileClasspath to name of the file in test/resources
-     * @return the content of the file as a String
-     * @throws IOException when there is an IO issue
-     */
-    public static String getResourceFileContent(final Resource fileClasspath) {
-        String fileContent = null;
-        try {
-            fileContent = StreamUtils.copyToString(fileClasspath.getInputStream(), StandardCharsets.UTF_8);
-        } catch (final IOException ioException) {
-            log.debug("unable to read resource file content. cause : {}", ioException.getMessage());
-        }
-        return fileContent;
-    }
-}
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml
deleted file mode 100644 (file)
index b78a5b2..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-#  ============LICENSE_START=======================================================
-#  Copyright (C) 2023-2024 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=========================================================
-server:
-    port: 8092
-    jetty:
-        threads:
-            max: 25
-
-rest:
-    api:
-        dmi-stub-base-path: /dmi
-
-spring:
-    main:
-        banner-mode: "off"
-    application:
-        name: "dmi-plugin-demo-and-csit-stub"
-
-    kafka:
-        bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVER:localhost:19092}
-        security:
-            protocol: PLAINTEXT
-        producer:
-            value-serializer: io.cloudevents.kafka.CloudEventSerializer
-            client-id: cps-core
-
-management:
-    endpoints:
-        web:
-            exposure:
-                include: health
-
-app:
-    ncmp:
-        async-m2m:
-            topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
-
-delay:
-    module-references-delay-ms: ${MODULE_REFERENCES_DELAY_MS:100}
-    module-resources-delay-ms: ${MODULE_RESOURCES_DELAY_MS:1000}
-    data-for-cm-handle-delay-ms: ${DATA_FOR_CM_HANDLE_DELAY_MS:2500}
-
-logging:
-    level:
-        org:
-            springframework:
-                web:
-                    filter:
-                        CommonsRequestLoggingFilter: DEBUG
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/data/operational/ietf-network-topology-sample-rfc8345.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/data/operational/ietf-network-topology-sample-rfc8345.json
deleted file mode 100644 (file)
index 8f9dbc2..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-{
-     "ietf-network:networks": {
-       "network": [
-         {
-           "network-types": {
-           },
-           "network-id": "otn-hc",
-           "node": [
-             {
-               "node-id": "D1",
-               "termination-point": [
-                 {
-                   "tp-id": "1-0-1"
-                 },
-                 {
-                   "tp-id": "1-2-1"
-                 },
-                 {
-                   "tp-id": "1-3-1"
-                 }
-               ]
-             },
-             {
-               "node-id": "D2",
-               "termination-point": [
-                 {
-                   "tp-id": "2-0-1"
-                 },
-                 {
-                   "tp-id": "2-1-1"
-                 },
-                 {
-                   "tp-id": "2-3-1"
-                 }
-               ]
-             },
-             {
-               "node-id": "D3",
-               "termination-point": [
-                 {
-                   "tp-id": "3-1-1"
-                 },
-                 {
-                   "tp-id": "3-2-1"
-                 }
-               ]
-             }
-           ],
-           "ietf-network-topology:link": [
-             {
-               "link-id": "D1,1-2-1,D2,2-1-1",
-               "source": {
-                 "source-node": "D1",
-                 "source-tp": "1-2-1"
-               },
-               "destination": {
-                 "dest-node": "D2",
-                 "dest-tp": "2-1-1"
-               }
-             },
-             {
-               "link-id": "D2,2-1-1,D1,1-2-1",
-               "source": {
-                 "source-node": "D2",
-                 "source-tp": "2-1-1"
-               },
-               "destination": {
-                 "dest-node": "D1",
-                 "dest-tp": "1-2-1"
-               }
-             }
-           ]
-         }
-       ]
-     }
-   }
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/ietfYang-ModuleResourcesResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/ietfYang-ModuleResourcesResponse.json
deleted file mode 100644 (file)
index 4326733..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-[
-       {
-               "moduleName": "ietf-yang-types-1",
-               "revision": "2013-07-15",
-               "yangSource": "module ietf-yang-types-1 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-15 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-2",
-               "revision": "2013-07-16",
-               "yangSource": "module ietf-yang-types-2 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-2\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-16 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-3",
-               "revision": "2013-07-17",
-               "yangSource": "module ietf-yang-types-3 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-3\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-17 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-4",
-               "revision": "2013-07-18",
-               "yangSource": "module ietf-yang-types-4 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-4\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-18 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-5",
-               "revision": "2013-07-19",
-               "yangSource": "module ietf-yang-types-5 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-5\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-19 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-6",
-               "revision": "2013-07-20",
-               "yangSource": "module ietf-yang-types-6 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-6\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-20 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-7",
-               "revision": "2013-07-21",
-               "yangSource": "module ietf-yang-types-7 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-7\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-21 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-8",
-               "revision": "2013-07-22",
-               "yangSource": "module ietf-yang-types-8 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-8\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-22 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-9",
-               "revision": "2013-07-23",
-               "yangSource": "module ietf-yang-types-9 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-9\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-23 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-10",
-               "revision": "2013-07-24",
-               "yangSource": "module ietf-yang-types-10 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-10\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-24 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       }
-]
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/ietfYang-ModuleResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/ietfYang-ModuleResponse.json
deleted file mode 100644 (file)
index 2cbd8d1..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-{
-       "schemas": [
-               {
-                       "moduleName": "ietf-yang-types-1",
-                       "revision": "2013-07-15"
-               },
-               {
-                       "moduleName": "ietf-yang-types-2",
-                       "revision": "2013-07-16"
-               },
-               {
-                       "moduleName": "ietf-yang-types-3",
-                       "revision": "2013-07-17"
-               },
-               {
-                       "moduleName": "ietf-yang-types-4",
-                       "revision": "2013-07-18"
-               },
-               {
-                       "moduleName": "ietf-yang-types-5",
-                       "revision": "2013-07-19"
-               },
-               {
-                       "moduleName": "ietf-yang-types-6",
-                       "revision": "2013-07-20"
-               },
-               {
-                       "moduleName": "ietf-yang-types-7",
-                       "revision": "2013-07-21"
-               },
-               {
-                       "moduleName": "ietf-yang-types-8",
-                       "revision": "2013-07-22"
-               },
-               {
-                       "moduleName": "ietf-yang-types-9",
-                       "revision": "2013-07-23"
-               },
-               {
-                       "moduleName": "ietf-yang-types-10",
-                       "revision": "2013-07-24"
-               }
-       ]
-}
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagA-ModuleResourcesResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagA-ModuleResourcesResponse.json
deleted file mode 100644 (file)
index 5d71391..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-[
-       {
-               "moduleName": "M1",
-               "revision": "2024-01-01",
-               "yangSource": "module M1 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-01 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "M2",
-               "revision": "2024-01-02",
-               "yangSource": "module M2 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M2\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-02 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       }
-]
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagA-ModuleResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagA-ModuleResponse.json
deleted file mode 100644 (file)
index 9f20564..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-       "schemas": [
-               {
-                       "moduleName": "M1",
-                       "revision": "2024-01-01"
-               },
-               {
-                       "moduleName": "M2",
-                       "revision": "2024-01-02"
-               }
-       ]
-}
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagB-ModuleResourcesResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagB-ModuleResourcesResponse.json
deleted file mode 100644 (file)
index ef9b85f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-[
-       {
-               "moduleName": "M1",
-               "revision": "2024-01-01",
-               "yangSource": "module M1 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-01 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "M3",
-               "revision": "2024-01-03",
-               "yangSource": "module M3 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M3\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-03 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       }
-]
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagB-ModuleResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagB-ModuleResponse.json
deleted file mode 100644 (file)
index 513c749..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-       "schemas": [
-               {
-                       "moduleName": "M1",
-                       "revision": "2024-01-01"
-               },
-               {
-                       "moduleName": "M3",
-                       "revision": "2024-01-03"
-               }
-       ]
-}
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagC-ModuleResourcesResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagC-ModuleResourcesResponse.json
deleted file mode 100644 (file)
index 8fb696b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-[
-       {
-               "moduleName": "M4",
-               "revision": "2024-01-04",
-               "yangSource": "module M4 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-04 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "M5",
-               "revision": "2024-01-05",
-               "yangSource": "module M5 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M2\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-05 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       }
-]
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagC-ModuleResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagC-ModuleResponse.json
deleted file mode 100644 (file)
index ea22d8b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-       "schemas": [
-               {
-                       "moduleName": "M4",
-                       "revision": "2024-01-04"
-               },
-               {
-                       "moduleName": "M5",
-                       "revision": "2024-01-05"
-               }
-       ]
-}
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagD-ModuleResourcesResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagD-ModuleResourcesResponse.json
deleted file mode 100644 (file)
index 5d71391..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-[
-       {
-               "moduleName": "M1",
-               "revision": "2024-01-01",
-               "yangSource": "module M1 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-01 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "M2",
-               "revision": "2024-01-02",
-               "yangSource": "module M2 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M2\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-02 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       }
-]
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagD-ModuleResponse.json b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/module/tagD-ModuleResponse.json
deleted file mode 100644 (file)
index 9f20564..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-       "schemas": [
-               {
-                       "moduleName": "M1",
-                       "revision": "2024-01-01"
-               },
-               {
-                       "moduleName": "M2",
-                       "revision": "2024-01-02"
-               }
-       ]
-}
\ No newline at end of file
diff --git a/dmi-plugin-demo-and-csit-stub/pom.xml b/dmi-plugin-demo-and-csit-stub/pom.xml
deleted file mode 100644 (file)
index 0719272..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ============LICENSE_START=======================================================
-  Copyright (C) 2023 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=========================================================
--->
-
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.onap.cps</groupId>
-        <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
-        <relativePath>../cps-parent/pom.xml</relativePath>
-    </parent>
-
-    <artifactId>dmi-plugin-demo-and-csit-stub</artifactId>
-    <packaging>pom</packaging>
-
-    <properties>
-        <parent.directory>${project.parent.basedir}/..</parent.directory>
-        <sonar.skip>true</sonar.skip>
-        <jacoco.skip>true</jacoco.skip>
-    </properties>
-
-    <modules>
-        <module>dmi-plugin-demo-and-csit-stub-service</module>
-        <module>dmi-plugin-demo-and-csit-stub-app</module>
-    </modules>
-</project>
\ No newline at end of file
diff --git a/docker-compose/config/grafana/jvm-micrometer-dashboard.json b/docker-compose/config/grafana/jvm-micrometer-dashboard.json
new file mode 100644 (file)
index 0000000..8f7747c
--- /dev/null
@@ -0,0 +1,3613 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": {
+          "type": "datasource",
+          "uid": "grafana"
+        },
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "limit": 100,
+        "name": "Annotations & Alerts",
+        "showIn": 0,
+        "type": "dashboard"
+      },
+      {
+        "datasource": {
+          "type": "prometheus",
+          "uid": "PBFA97CFB590B2093"
+        },
+        "enable": true,
+        "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0",
+        "iconColor": "rgba(255, 96, 96, 1)",
+        "name": "Restart Detection",
+        "showIn": 0,
+        "step": "1m",
+        "tagKeys": "restart-tag",
+        "textFormat": "uptime reset",
+        "titleFormat": "Restart"
+      }
+    ]
+  },
+  "description": "Dashboard for Micrometer instrumented applications (Java, Spring Boot, Micronaut)",
+  "editable": true,
+  "fiscalYearStartMonth": 0,
+  "gnetId": 4701,
+  "graphTooltip": 1,
+  "id": 1,
+  "links": [],
+  "panels": [
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 0
+      },
+      "id": 139,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "Quick Facts",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "decimals": 1,
+          "mappings": [
+            {
+              "options": {
+                "match": "null",
+                "result": {
+                  "text": "N/A"
+                }
+              },
+              "type": "special"
+            }
+          ],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 3,
+        "w": 6,
+        "x": 0,
+        "y": 1
+      },
+      "id": 63,
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "none",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "percentChangeColorMode": "standard",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "showPercentChange": false,
+        "textMode": "auto",
+        "wideLayout": true
+      },
+      "pluginVersion": "11.1.4",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "",
+          "metric": "",
+          "refId": "A",
+          "step": 14400
+        }
+      ],
+      "title": "Uptime",
+      "type": "stat"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "mappings": [
+            {
+              "options": {
+                "match": "null",
+                "result": {
+                  "text": "N/A"
+                }
+              },
+              "type": "special"
+            }
+          ],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "dateTimeAsIso"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 3,
+        "w": 6,
+        "x": 6,
+        "y": 1
+      },
+      "id": 92,
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "none",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "percentChangeColorMode": "standard",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "showPercentChange": false,
+        "textMode": "auto",
+        "wideLayout": true
+      },
+      "pluginVersion": "11.1.4",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "",
+          "metric": "",
+          "refId": "A",
+          "step": 14400
+        }
+      ],
+      "title": "Start time",
+      "type": "stat"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "decimals": 2,
+          "mappings": [
+            {
+              "options": {
+                "match": "null",
+                "result": {
+                  "text": "N/A"
+                }
+              },
+              "type": "special"
+            }
+          ],
+          "max": 100,
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "rgba(50, 172, 45, 0.97)",
+                "value": null
+              },
+              {
+                "color": "rgba(237, 129, 40, 0.89)",
+                "value": 70
+              },
+              {
+                "color": "rgba(245, 54, 54, 0.9)",
+                "value": 90
+              }
+            ]
+          },
+          "unit": "percent"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 3,
+        "w": 6,
+        "x": 12,
+        "y": 1
+      },
+      "id": 65,
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "none",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "percentChangeColorMode": "standard",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "showPercentChange": false,
+        "textMode": "auto",
+        "wideLayout": true
+      },
+      "pluginVersion": "11.1.4",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "",
+          "refId": "A",
+          "step": 14400
+        }
+      ],
+      "title": "Heap used",
+      "type": "stat"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "decimals": 2,
+          "mappings": [
+            {
+              "options": {
+                "match": "null",
+                "result": {
+                  "text": "N/A"
+                }
+              },
+              "type": "special"
+            },
+            {
+              "options": {
+                "from": -1e+32,
+                "result": {
+                  "text": "N/A"
+                },
+                "to": 0
+              },
+              "type": "range"
+            }
+          ],
+          "max": 100,
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "rgba(50, 172, 45, 0.97)",
+                "value": null
+              },
+              {
+                "color": "rgba(237, 129, 40, 0.89)",
+                "value": 70
+              },
+              {
+                "color": "rgba(245, 54, 54, 0.9)",
+                "value": 90
+              }
+            ]
+          },
+          "unit": "percent"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 3,
+        "w": 6,
+        "x": 18,
+        "y": 1
+      },
+      "id": 75,
+      "maxDataPoints": 100,
+      "options": {
+        "colorMode": "value",
+        "graphMode": "none",
+        "justifyMode": "auto",
+        "orientation": "horizontal",
+        "percentChangeColorMode": "standard",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "showPercentChange": false,
+        "textMode": "auto",
+        "wideLayout": true
+      },
+      "pluginVersion": "11.1.4",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "",
+          "refId": "A",
+          "step": 14400
+        }
+      ],
+      "title": "Non-Heap used",
+      "type": "stat"
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 4
+      },
+      "id": 140,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "I/O Overview",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "ops"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 0,
+        "y": 5
+      },
+      "id": 111,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\"}[1m]))",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "HTTP",
+          "refId": "A"
+        }
+      ],
+      "title": "Rate",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "ops"
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "HTTP"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#890f02",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "HTTP - 5xx"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#bf1b00",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 6,
+        "y": 5
+      },
+      "id": 112,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status=~\"5..\"}[1m]))",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "HTTP - 5xx",
+          "refId": "A"
+        }
+      ],
+      "title": "Errors",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 12,
+        "y": 5
+      },
+      "id": 113,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(rate(http_server_requests_seconds_sum{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 1,
+          "legendFormat": "HTTP - AVG",
+          "refId": "A"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "max(http_server_requests_seconds_max{application=\"$application\", instance=\"$instance\", status!~\"5..\"})",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 1,
+          "legendFormat": "HTTP - MAX",
+          "refId": "B"
+        }
+      ],
+      "title": "Duration",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 18,
+        "y": 5
+      },
+      "id": 119,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "tomcat_threads_busy_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "TOMCAT - BSY",
+          "refId": "A"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "tomcat_threads_current_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "TOMCAT - CUR",
+          "refId": "B"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "tomcat_threads_config_max_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "TOMCAT - MAX",
+          "refId": "C"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jetty_threads_busy{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "JETTY - BSY",
+          "refId": "D"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jetty_threads_current{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "JETTY - CUR",
+          "refId": "E"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jetty_threads_config_max{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "JETTY - MAX",
+          "refId": "F"
+        }
+      ],
+      "title": "Utilisation",
+      "type": "timeseries"
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 12
+      },
+      "id": 141,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "JVM Memory",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 0,
+        "y": 13
+      },
+      "id": 24,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "used",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "committed",
+          "refId": "B",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "max",
+          "refId": "C",
+          "step": 2400
+        }
+      ],
+      "title": "JVM Heap",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 6,
+        "y": 13
+      },
+      "id": 25,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "used",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "committed",
+          "refId": "B",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "max",
+          "refId": "C",
+          "step": 2400
+        }
+      ],
+      "title": "JVM Non-Heap",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 12,
+        "y": 13
+      },
+      "id": 26,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "used",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "committed",
+          "refId": "B",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\"})",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "max",
+          "refId": "C",
+          "step": 2400
+        }
+      ],
+      "title": "JVM Total",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 18,
+        "y": 13
+      },
+      "id": 86,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": true,
+          "intervalFactor": 2,
+          "legendFormat": "vss",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "rss",
+          "refId": "B"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "swap",
+          "refId": "C"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "total",
+          "refId": "D"
+        }
+      ],
+      "title": "JVM Process Memory",
+      "type": "timeseries"
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 20
+      },
+      "id": 142,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "JVM Misc",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "decimals": 1,
+          "mappings": [],
+          "max": 1,
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "percentunit"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 0,
+        "y": 21
+      },
+      "id": 106,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "system_cpu_usage{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 1,
+          "legendFormat": "system",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_cpu_usage{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 1,
+          "legendFormat": "process",
+          "refId": "B"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "avg_over_time(process_cpu_usage{application=\"$application\", instance=\"$instance\"}[15m])",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 1,
+          "legendFormat": "process-15m",
+          "refId": "C"
+        }
+      ],
+      "title": "CPU Usage",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "decimals": 1,
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 6,
+        "y": 21
+      },
+      "id": 93,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "system_load_average_1m{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "system-1m",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "system_cpu_count{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "cpus",
+          "refId": "B"
+        }
+      ],
+      "title": "Load",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "decimals": 0,
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 12,
+        "y": 21
+      },
+      "id": 32,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_threads_live_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "live",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_threads_daemon_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "daemon",
+          "metric": "",
+          "refId": "B",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_threads_peak_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "peak",
+          "refId": "C",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "process",
+          "refId": "D",
+          "step": 2400
+        }
+      ],
+      "title": "Threads",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "blocked"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#bf1b00",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "new"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#fce2de",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "runnable"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#7eb26d",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "terminated"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#511749",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "timed-waiting"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#c15c17",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "waiting"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#eab839",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 18,
+        "y": 21
+      },
+      "id": 124,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_threads_states_threads{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "{{state}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Thread States",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "description": "The percent of time spent on Garbage Collection over all CPUs assigned to the JVM process.",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "decimals": 1,
+          "mappings": [],
+          "max": 1,
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "percentunit"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 0,
+        "y": 28
+      },
+      "id": 138,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "sum(rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])) by (application, instance) / on(application, instance) system_cpu_count",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "CPU time spent on GC",
+          "refId": "A"
+        }
+      ],
+      "title": "GC Pressure",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "decimals": 0,
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "opm"
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "debug"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#1F78C1",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "error"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#BF1B00",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "info"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#508642",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "trace"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#6ED0E0",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "warn"
+            },
+            "properties": [
+              {
+                "id": "color",
+                "value": {
+                  "fixedColor": "#EAB839",
+                  "mode": "fixed"
+                }
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 6,
+        "y": 28
+      },
+      "id": 91,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "increase(logback_events_total{application=\"$application\", instance=\"$instance\"}[1m])",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "{{level}}",
+          "metric": "",
+          "refId": "A",
+          "step": 1200
+        }
+      ],
+      "title": "Log Events",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "log": 10,
+              "type": "log"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "decimals": 0,
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "short"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 6,
+        "x": 18,
+        "y": 28
+      },
+      "id": 61,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_files_open_files{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "open",
+          "metric": "",
+          "refId": "A",
+          "step": 2400
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "process_files_max_files{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "max",
+          "metric": "",
+          "refId": "B",
+          "step": 2400
+        }
+      ],
+      "title": "File Descriptors",
+      "type": "timeseries"
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 35
+      },
+      "id": 143,
+      "panels": [],
+      "repeat": "persistence_counts",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "JVM Memory Pools (Heap)",
+      "type": "row"
+    },
+    {
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisBorderShow": false,
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "insertNulls": false,
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 0,
+        "y": 36
+      },
+      "id": 3,
+      "options": {
+        "legend": {
+          "calcs": [
+            "lastNotNull",
+            "max"
+          ],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "multi",
+          "sort": "none"
+        }
+      },
+      "repeat": "jvm_memory_pool_heap",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}",
+          "format": "time_series",
+          "hide": false,
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "used",
+          "metric": "",
+          "refId": "A",
+          "step": 1800
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}",
+          "format": "time_series",
+          "hide": false,
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "commited",
+          "metric": "",
+          "refId": "B",
+          "step": 1800
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}",
+          "format": "time_series",
+          "hide": false,
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "max",
+          "metric": "",
+          "refId": "C",
+          "step": 1800
+        }
+      ],
+      "title": "$jvm_memory_pool_heap",
+      "type": "timeseries"
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 43
+      },
+      "id": 144,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "JVM Memory Pools (Non-Heap)",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "autoMigrateFrom": "graph",
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "grid": {
+        "leftLogBase": 1,
+        "rightLogBase": 1
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 0,
+        "y": 44
+      },
+      "id": 78,
+      "legend": {
+        "alignAsTable": false,
+        "avg": false,
+        "current": true,
+        "max": true,
+        "min": false,
+        "rightSide": false,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "maxPerRow": 3,
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "repeat": "jvm_memory_pool_nonheap",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}",
+          "format": "time_series",
+          "hide": false,
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "used",
+          "metric": "",
+          "refId": "A",
+          "step": 1800
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}",
+          "format": "time_series",
+          "hide": false,
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "commited",
+          "metric": "",
+          "refId": "B",
+          "step": 1800
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}",
+          "format": "time_series",
+          "hide": false,
+          "interval": "",
+          "intervalFactor": 2,
+          "legendFormat": "max",
+          "metric": "",
+          "refId": "C",
+          "step": 1800
+        }
+      ],
+      "thresholds": [],
+      "title": "$jvm_memory_pool_nonheap",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "timeseries",
+      "x-axis": true,
+      "xaxis": {
+        "mode": "time",
+        "show": true,
+        "values": []
+      },
+      "y-axis": true,
+      "y_formats": [
+        "mbytes",
+        "short"
+      ],
+      "yaxes": [
+        {
+          "format": "bytes",
+          "logBase": 1,
+          "min": 0,
+          "show": true
+        },
+        {
+          "format": "short",
+          "logBase": 1,
+          "show": true
+        }
+      ]
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 58
+      },
+      "id": 145,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "Garbage Collection",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "autoMigrateFrom": "graph",
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 0,
+        "y": 59
+      },
+      "id": 98,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 1,
+          "legendFormat": "{{action}} ({{cause}})",
+          "refId": "A"
+        }
+      ],
+      "thresholds": [],
+      "title": "Collections",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "timeseries",
+      "xaxis": {
+        "mode": "time",
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "ops",
+          "logBase": 1,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "show": true
+        }
+      ]
+    },
+    {
+      "aliasColors": {},
+      "autoMigrateFrom": "graph",
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 8,
+        "y": 59
+      },
+      "id": 101,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "intervalFactor": 1,
+          "legendFormat": "avg {{action}} ({{cause}})",
+          "refId": "A"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_gc_pause_seconds_max{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "intervalFactor": 1,
+          "legendFormat": "max {{action}} ({{cause}})",
+          "refId": "B"
+        }
+      ],
+      "thresholds": [],
+      "title": "Pause Durations",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "timeseries",
+      "xaxis": {
+        "mode": "time",
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "s",
+          "logBase": 1,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "show": true
+        }
+      ]
+    },
+    {
+      "aliasColors": {},
+      "autoMigrateFrom": "graph",
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 16,
+        "y": 59
+      },
+      "id": 99,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "rate(jvm_gc_memory_allocated_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "allocated",
+          "refId": "A"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "rate(jvm_gc_memory_promoted_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "promoted",
+          "refId": "B"
+        }
+      ],
+      "thresholds": [],
+      "title": "Allocated/Promoted",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "timeseries",
+      "xaxis": {
+        "mode": "time",
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "Bps",
+          "logBase": 1,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "logBase": 1,
+          "show": true
+        }
+      ]
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 66
+      },
+      "id": 146,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "Classloading",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "autoMigrateFrom": "graph",
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "grid": {
+        "leftLogBase": 1,
+        "rightLogBase": 1
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 0,
+        "y": 67
+      },
+      "id": 37,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_classes_loaded_classes{application=\"$application\", instance=\"$instance\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "loaded",
+          "metric": "",
+          "refId": "A",
+          "step": 1200
+        }
+      ],
+      "thresholds": [],
+      "title": "Classes loaded",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "timeseries",
+      "x-axis": true,
+      "xaxis": {
+        "mode": "time",
+        "show": true,
+        "values": []
+      },
+      "y-axis": true,
+      "y_formats": [
+        "short",
+        "short"
+      ],
+      "yaxes": [
+        {
+          "format": "short",
+          "logBase": 1,
+          "min": 0,
+          "show": true
+        },
+        {
+          "format": "short",
+          "logBase": 1,
+          "show": true
+        }
+      ]
+    },
+    {
+      "aliasColors": {},
+      "autoMigrateFrom": "graph",
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "grid": {
+        "leftLogBase": 1,
+        "rightLogBase": 1
+      },
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 12,
+        "y": 67
+      },
+      "id": 38,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "delta(jvm_classes_loaded_classes{application=\"$application\",instance=\"$instance\"}[1m])",
+          "format": "time_series",
+          "hide": false,
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "delta-1m",
+          "metric": "",
+          "refId": "A",
+          "step": 1200
+        }
+      ],
+      "thresholds": [],
+      "title": "Class delta",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "timeseries",
+      "x-axis": true,
+      "xaxis": {
+        "mode": "time",
+        "show": true,
+        "values": []
+      },
+      "y-axis": true,
+      "y_formats": [
+        "ops",
+        "short"
+      ],
+      "yaxes": [
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "show": true
+        },
+        {
+          "format": "short",
+          "logBase": 1,
+          "show": true
+        }
+      ]
+    },
+    {
+      "collapsed": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 74
+      },
+      "id": 147,
+      "panels": [],
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "refId": "A"
+        }
+      ],
+      "title": "Buffer Pools",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "autoMigrateFrom": "graph",
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": {
+        "type": "prometheus",
+        "uid": "PBFA97CFB590B2093"
+      },
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 0,
+        "y": 75
+      },
+      "id": 131,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "maxPerRow": 3,
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "repeat": "jvm_buffer_pool",
+      "seriesOverrides": [
+        {
+          "alias": "count",
+          "yaxis": 2
+        },
+        {
+          "alias": "buffers",
+          "yaxis": 2
+        }
+      ],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "used",
+          "refId": "A"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}",
+          "format": "time_series",
+          "intervalFactor": 2,
+          "legendFormat": "capacity",
+          "refId": "B"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "PBFA97CFB590B2093"
+          },
+          "expr": "jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}",
+          "format": "time_series",
+          "hide": false,
+          "intervalFactor": 2,
+          "legendFormat": "buffers",
+          "refId": "C"
+        }
+      ],
+      "thresholds": [],
+      "title": "$jvm_buffer_pool",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "timeseries",
+      "xaxis": {
+        "mode": "time",
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "decbytes",
+          "logBase": 1,
+          "min": "0",
+          "show": true
+        },
+        {
+          "decimals": 0,
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "min": "0",
+          "show": true
+        }
+      ]
+    }
+  ],
+  "refresh": "auto",
+  "schemaVersion": 39,
+  "tags": [],
+  "templating": {
+    "list": [
+      {
+        "current": {
+          "isNone": true,
+          "selected": false,
+          "text": "None",
+          "value": ""
+        },
+        "datasource": {
+          "type": "prometheus",
+          "uid": "PBFA97CFB590B2093"
+        },
+        "definition": "",
+        "hide": 0,
+        "includeAll": false,
+        "label": "Application",
+        "multi": false,
+        "name": "application",
+        "options": [],
+        "query": "label_values(application)",
+        "refresh": 2,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 0,
+        "tagValuesQuery": "",
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      },
+      {
+        "allFormat": "glob",
+        "current": {
+          "selected": false,
+          "text": "docker-compose-cps-and-ncmp-1:8080",
+          "value": "docker-compose-cps-and-ncmp-1:8080"
+        },
+        "datasource": {
+          "type": "prometheus",
+          "uid": "PBFA97CFB590B2093"
+        },
+        "definition": "",
+        "hide": 0,
+        "includeAll": false,
+        "label": "Instance",
+        "multi": false,
+        "multiFormat": "glob",
+        "name": "instance",
+        "options": [],
+        "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)",
+        "refresh": 2,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 0,
+        "tagValuesQuery": "",
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      },
+      {
+        "allFormat": "glob",
+        "current": {
+          "selected": false,
+          "text": "All",
+          "value": "$__all"
+        },
+        "datasource": {
+          "type": "prometheus",
+          "uid": "PBFA97CFB590B2093"
+        },
+        "definition": "",
+        "hide": 2,
+        "includeAll": true,
+        "label": "JVM Memory Pools Heap",
+        "multi": false,
+        "multiFormat": "glob",
+        "name": "jvm_memory_pool_heap",
+        "options": [],
+        "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)",
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 1,
+        "tagValuesQuery": "",
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      },
+      {
+        "allFormat": "glob",
+        "current": {
+          "selected": false,
+          "text": "All",
+          "value": "$__all"
+        },
+        "datasource": {
+          "type": "prometheus",
+          "uid": "PBFA97CFB590B2093"
+        },
+        "definition": "",
+        "hide": 2,
+        "includeAll": true,
+        "label": "JVM Memory Pools Non-Heap",
+        "multi": false,
+        "multiFormat": "glob",
+        "name": "jvm_memory_pool_nonheap",
+        "options": [],
+        "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)",
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 2,
+        "tagValuesQuery": "",
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      },
+      {
+        "allFormat": "glob",
+        "current": {
+          "selected": false,
+          "text": "All",
+          "value": "$__all"
+        },
+        "datasource": {
+          "type": "prometheus",
+          "uid": "PBFA97CFB590B2093"
+        },
+        "definition": "",
+        "hide": 2,
+        "includeAll": true,
+        "label": "JVM Buffer Pools",
+        "multi": false,
+        "multiFormat": "glob",
+        "name": "jvm_buffer_pool",
+        "options": [],
+        "query": "label_values(jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\"},id)",
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 1,
+        "tagValuesQuery": "",
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      }
+    ]
+  },
+  "time": {
+    "from": "now-30m",
+    "to": "now"
+  },
+  "timepicker": {
+    "now": true,
+    "refresh_intervals": [
+      "5s",
+      "10s",
+      "30s",
+      "1m",
+      "5m",
+      "15m",
+      "30m",
+      "1h",
+      "2h",
+      "1d"
+    ],
+    "time_options": [
+      "5m",
+      "15m",
+      "1h",
+      "6h",
+      "12h",
+      "24h",
+      "2d",
+      "7d",
+      "30d"
+    ]
+  },
+  "timezone": "browser",
+  "title": "JVM (Micrometer)",
+  "uid": "bdvp1kgecrda8f",
+  "version": 1,
+  "weekStart": ""
+}
diff --git a/docker-compose/config/grafana/provisioning/dashboards/dashboard.yml b/docker-compose/config/grafana/provisioning/dashboards/dashboard.yml
new file mode 100644 (file)
index 0000000..b6dba86
--- /dev/null
@@ -0,0 +1,9 @@
+apiVersion: 1
+
+providers:
+  - name: default
+    orgId: 1
+    type: file
+    options:
+      path: /var/lib/grafana/dashboards
+      foldersFromFilesStructure: true
diff --git a/docker-compose/config/grafana/provisioning/datasources/datasource.yml b/docker-compose/config/grafana/provisioning/datasources/datasource.yml
new file mode 100644 (file)
index 0000000..86fd346
--- /dev/null
@@ -0,0 +1,8 @@
+apiVersion: 1
+
+datasources:
+  - name: Prometheus
+    type: prometheus
+    access: proxy
+    url: http://prometheus:9090
+    isDefault: true
diff --git a/docker-compose/config/nginx/nginx.conf b/docker-compose/config/nginx/nginx.conf
new file mode 100644 (file)
index 0000000..17fad1b
--- /dev/null
@@ -0,0 +1,52 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2024 Nordix Foundation. All rights reserved.
+#  ========================================================================
+#  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.
+#  ============LICENSE_END=================================================
+
+events { }
+
+http {
+
+    # Add more server entries here for scaling or load balancing
+    upstream cps-and-ncmp {
+        server cps-and-ncmp:8080;
+    }
+
+    server {
+        listen 80;
+
+        # Root location proxying
+        location / {
+            proxy_pass http://cps-and-ncmp;
+            include /etc/nginx/proxy_params;    # Include common proxy parameters
+        }
+
+        location /swagger-ui/ {     # Swagger UI location
+            proxy_pass http://cps-and-ncmp/swagger-ui/;
+        }
+
+        location /v3/api-docs/ {    # API docs location
+            proxy_pass http://cps-and-ncmp/v3/api-docs/;
+        }
+
+        location /actuator/health { # Actuator health endpoint
+            proxy_pass http://cps-and-ncmp/actuator/health;
+        }
+
+        location /actuator/health/readiness {    # Actuator readiness endpoint
+            proxy_pass http://cps-and-ncmp/actuator/health/readiness;
+        }
+
+    }
+}
diff --git a/docker-compose/config/nginx/proxy_params b/docker-compose/config/nginx/proxy_params
new file mode 100644 (file)
index 0000000..ab092a9
--- /dev/null
@@ -0,0 +1,4 @@
+proxy_set_header Host $host;
+proxy_set_header X-Real-IP $remote_addr;
+proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+proxy_set_header X-Forwarded-Proto $scheme;
\ No newline at end of file
similarity index 53%
rename from docker-compose/prometheus.yml
rename to docker-compose/config/prometheus.yml
index 9640709..89af4a6 100644 (file)
@@ -3,8 +3,10 @@ global:
   evaluation_interval: 5s
 
 scrape_configs:
-  - job_name: 'cps-and-ncm'
+  - job_name: 'cps-and-ncmp'
     metrics_path: '/actuator/prometheus'
     scrape_interval: 5s
     static_configs:
-      - targets: ['cps-and-ncmp:8080']
+      - targets:
+        - 'docker-compose-cps-and-ncmp-1:8080'
+        - 'docker-compose-cps-and-ncmp-2:8080'
index a604b06..1e47d47 100644 (file)
@@ -20,6 +20,8 @@ services:
 
   ### docker-compose --profile dmi-service up -d -> run CPS services incl. dmi-plugin ###
   ### docker-compose --profile dmi-stub --profile monitoring up -d -> run CPS with stubbed dmi-plugin (for registration performance testing)
+  ### docker-compose --profile dmi-stub --profile tracing up -d -> run CPS with stubbed dmi-plugin (for open telemetry tracing testing make ONAP_TRACING_ENABLED "true" later "http://localhost:16686" can be accessed from browser)
+  ### docker-compose --profile dmi-stub --profile policy-executor-stub up -d -> run CPS with stubbed dmi-plugin and policy executor stub (for policy executor service testing make POLICY_SERVICE_ENABLED "true")
   ### to disable notifications make notification.enabled to false & comment out kafka/zookeeper services ###
 
   dbpostgresql:
@@ -32,7 +34,7 @@ services:
       POSTGRES_USER: ${DB_USERNAME:-cps}
       POSTGRES_PASSWORD: ${DB_PASSWORD:-cps}
     volumes:
-      - ./postgres-init.sql:/docker-entrypoint-initdb.d/postgres-init.sql
+      - ./config/postgres-init.sql:/docker-entrypoint-initdb.d/postgres-init.sql
     deploy:
       resources:
         reservations:
@@ -43,11 +45,7 @@ services:
           memory: 3G
 
   cps-and-ncmp:
-    container_name: cps-and-ncmp
     image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/cps-and-ncmp:${CPS_VERSION:-latest}
-    ports:
-      - ${CPS_CORE_PORT:-8883}:8080
-      - ${CPS_CORE_DEBUG_PORT:-5005}:5005
     environment:
       CPS_USERNAME: ${CPS_CORE_USERNAME:-cpsuser}
       CPS_PASSWORD: ${CPS_CORE_PASSWORD:-cpsr0cks!}
@@ -58,11 +56,15 @@ services:
       DMI_PASSWORD: ${DMI_PASSWORD:-cpsr0cks!}
       KAFKA_BOOTSTRAP_SERVER: kafka:29092
       notification.enabled: 'true'
-      JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+      ONAP_TRACING_ENABLED: 'false'
+      ONAP_OTEL_SAMPLER_JAEGER_REMOTE_ENDPOINT: http://jaeger-service:14250
+      ONAP_OTEL_EXPORTER_ENDPOINT: http://jaeger-service:4317
+      POLICY_SERVICE_ENABLED: 'false'
     restart: unless-stopped
     depends_on:
       - dbpostgresql
     deploy:
+      replicas: 2
       resources:
         reservations:
           cpus: '2'
@@ -71,6 +73,17 @@ services:
           cpus: '3'
           memory: 3G
 
+  nginx:
+    container_name: nginx-loadbalancer
+    image: nginx:latest
+    ports:
+      - ${CPS_CORE_PORT:-8883}:80
+    depends_on:
+      - cps-and-ncmp
+    volumes:
+      - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf
+      - ./config/nginx/proxy_params:/etc/nginx/proxy_params
+
   ### if kafka is not required comment out zookeeper and kafka ###
   zookeeper:
     image: confluentinc/cp-zookeeper:6.2.1
@@ -96,7 +109,7 @@ services:
 
   ncmp-dmi-plugin:
     container_name: ncmp-dmi-plugin
-    image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/ncmp-dmi-plugin:${DMI_VERSION:-1.5.0-SNAPSHOT-latest}
+    image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/ncmp-dmi-plugin:${DMI_VERSION:-1.6.0-SNAPSHOT-latest}
     ports:
       - ${DMI_PORT:-8783}:8080
     environment:
@@ -128,35 +141,48 @@ services:
       KAFKA_BOOTSTRAP_SERVER: kafka:29092
       NCMP_CONSUMER_GROUP_ID: ncmp-group
       NCMP_ASYNC_M2M_TOPIC: ncmp-async-m2m
+      MODULE_INITIAL_PROCESSING_DELAY_MS: 0
       MODULE_REFERENCES_DELAY_MS: 100
       MODULE_RESOURCES_DELAY_MS: 1000
-      DATA_FOR_CM_HANDLE_DELAY_MS: 2500
+      READ_DATA_FOR_CM_HANDLE_DELAY_MS: 300
+      WRITE_DATA_FOR_CM_HANDLE_DELAY_MS: 670
     restart: unless-stopped
     profiles:
       - dmi-stub
       - dmi-service
 
+  policy-executor-stub:
+    container_name: policy-executor-stub
+    image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/policy-executor-stub:latest
+    ports:
+      - 8785:8093
+    restart: unless-stopped
+    profiles:
+      - policy-executor-stub
+
   prometheus:
-    container_name: prometheus-container
+    container_name: prometheus
     image: prom/prometheus:latest
     ports:
       - 9090:9090
     restart: always
     volumes:
-      - ./prometheus.yml:/etc/prometheus/prometheus.yml
+      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
     profiles:
       - monitoring
 
   grafana:
     image: grafana/grafana-oss:latest
     user: ""
-    container_name: grafana-container
+    container_name: grafana
     depends_on:
       prometheus:
         condition: service_started
     ports:
       - 3000:3000
     volumes:
+      - ./config/grafana/provisioning/:/etc/grafana/provisioning/
+      - ./config/grafana/jvm-micrometer-dashboard.json:/var/lib/grafana/dashboards/jvm-micrometer-dashboard.json
       - grafana:/var/lib/grafana
     environment:
       - GF_SECURITY_ADMIN_PASSWORD=admin
@@ -164,5 +190,26 @@ services:
     profiles:
       - monitoring
 
+  kafka-ui:
+    container_name: kafka-ui
+    image: provectuslabs/kafka-ui:latest
+    ports:
+      - 8089:8080
+    environment:
+      DYNAMIC_CONFIG_ENABLED: 'true'
+      KAFKA_CLUSTERS_0_NAME: 'cps-kafka-local'
+      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092
+    profiles:
+      - monitoring
+
+  jaeger-service:
+    container_name: jaeger-service
+    image: jaegertracing/all-in-one:latest
+    ports:
+      - 16686:16686
+    restart: unless-stopped
+    profiles:
+      - tracing
+
 volumes:
   grafana:
index ab78ad9..07923d4 100644 (file)
Binary files a/docs/_static/cps-delta-mechanism.png and b/docs/_static/cps-delta-mechanism.png differ
diff --git a/docs/_static/logo_onap_2024.png b/docs/_static/logo_onap_2024.png
new file mode 100644 (file)
index 0000000..55d307f
Binary files /dev/null and b/docs/_static/logo_onap_2024.png differ
index 1c4d745..4a40f9b 100644 (file)
@@ -111,9 +111,9 @@ Execute CPS service that you want to calculate total elapsed time and log as sho
 
 .. code-block::
 
-   2022-01-28 18:39:17.679 DEBUG [cps-application,e17da1571e518c59,e17da1571e518c59] 11128 --- [tp1901272535-29] o.onap.cps.aop.CpsLoggingAspectService   : Execution time of : DataspaceRepository.getByName() with argument[s] = [test42] having result = org.onap.cps.spi.entities.DataspaceEntity@68ded236 :: 205 ms
+   2022-01-28 18:39:17.679 DEBUG [cps-application,e17da1571e518c59,e17da1571e518c59] 11128 --- [tp1901272535-29] o.onap.cps.aop.CpsLoggingAspectService   : Execution time of : DataspaceRepository.getByName() with argument[s] = [test42] having result = org.onap.cps.impl.models.DataspaceEntity@68ded236 :: 205 ms
 
-   2022-01-28 18:39:17.726 DEBUG [cps-application,e17da1571e518c59,e17da1571e518c59] 11128 --- [tp1901272535-29] o.onap.cps.aop.CpsLoggingAspectService   : Execution time of : AnchorRepository.getByDataspaceAndName() with argument[s] = [org.onap.cps.spi.entities.DataspaceEntity@68ded236, bookstore] having result = org.onap.cps.spi.entities.AnchorEntity@71c47fb1 :: 46 ms
+   2022-01-28 18:39:17.726 DEBUG [cps-application,e17da1571e518c59,e17da1571e518c59] 11128 --- [tp1901272535-29] o.onap.cps.aop.CpsLoggingAspectService   : Execution time of : AnchorRepository.getByDataspaceAndName() with argument[s] = [org.onap.cps.impl.models.DataspaceEntity@68ded236, bookstore] having result = org.onap.cps.impl.models.AnchorEntity@71c47fb1 :: 46 ms
 
    2022-01-28 18:39:17.768 DEBUG [cps-application,e17da1571e518c59,e17da1571e518c59] 11128 --- [tp1901272535-29] o.onap.cps.aop.CpsLoggingAspectService   : Execution time of : CpsAdminPersistenceServiceImpl.getAnchor() with argument[s] = [test42, bookstore] having result = Anchor(name=bookstore, dataspaceName=test42, schemaSetName=bookstore) :: 299 ms
 
index 2798b78..d6cca6e 100644 (file)
@@ -9,11 +9,7 @@ info:
     name: Apache 2.0
     url: http://www.apache.org/licenses/LICENSE-2.0
   title: ONAP Open API v3 Configuration Persistence Service
-  version: 1.0.0
-  x-planned-retirement-date: "202212"
-  x-component: Modeling
-  x-logo:
-    url: cps_logo.png
+  version: 3.5.2
 servers:
 - url: /cps/api
 security:
@@ -40,7 +36,7 @@ paths:
       responses:
         "201":
           content:
-            text/plain:
+            application/json:
               schema:
                 example: my-resource
                 type: string
@@ -364,7 +360,7 @@ paths:
       responses:
         "201":
           content:
-            text/plain:
+            application/json:
               schema:
                 example: my-resource
                 type: string
@@ -711,7 +707,7 @@ paths:
       responses:
         "201":
           content:
-            text/plain:
+            application/json:
               schema:
                 example: my-resource
                 type: string
@@ -1346,6 +1342,13 @@ paths:
         schema:
           example: 2021-03-21T00:10:34.030-0100
           type: string
+      - description: Content type in header
+        in: header
+        name: Content-Type
+        required: true
+        schema:
+          example: application/json
+          type: string
       requestBody:
         content:
           application/json:
@@ -1353,8 +1356,17 @@ paths:
               dataSample:
                 $ref: '#/components/examples/dataSample'
                 value: null
+            schema:
+              type: string
+          application/xml:
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSampleXml'
+                value: null
             schema:
               type: object
+              xml:
+                name: stores
         required: true
       responses:
         "200":
@@ -1446,7 +1458,7 @@ paths:
         schema:
           example: 2021-03-21T00:10:34.030-0100
           type: string
-      - description: Content type header
+      - description: Content type in header
         in: header
         name: Content-Type
         required: true
@@ -1475,7 +1487,7 @@ paths:
       responses:
         "201":
           content:
-            text/plain:
+            application/json:
               schema:
                 example: my-resource
                 type: string
@@ -1571,6 +1583,13 @@ paths:
         schema:
           example: 2021-03-21T00:10:34.030-0100
           type: string
+      - description: Content type in header
+        in: header
+        name: Content-Type
+        required: true
+        schema:
+          example: application/json
+          type: string
       requestBody:
         content:
           application/json:
@@ -1578,8 +1597,17 @@ paths:
               dataSample:
                 $ref: '#/components/examples/dataSample'
                 value: null
+            schema:
+              type: string
+          application/xml:
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSampleXml'
+                value: null
             schema:
               type: object
+              xml:
+                name: stores
         required: true
       responses:
         "200":
@@ -1746,6 +1774,13 @@ paths:
         schema:
           example: 2021-03-21T00:10:34.030-0100
           type: string
+      - description: Content type in header
+        in: header
+        name: Content-Type
+        required: true
+        schema:
+          example: application/json
+          type: string
       requestBody:
         content:
           application/json:
@@ -1753,13 +1788,22 @@ paths:
               dataSample:
                 $ref: '#/components/examples/dataSample'
                 value: null
+            schema:
+              type: string
+          application/xml:
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSampleXml'
+                value: null
             schema:
               type: object
+              xml:
+                name: stores
         required: true
       responses:
         "201":
           content:
-            text/plain:
+            application/json:
               schema:
                 example: my-resource
                 type: string
@@ -1896,7 +1940,7 @@ paths:
       summary: Replace list content
       tags:
       - cps-data
-  /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/delta:
+  /v2/dataspaces/{dataspace-name}/anchors/{source-anchor-name}/delta:
     get:
       description: Get delta between two anchors within a given dataspace
       operationId: getDeltaByDataspaceAndAnchors
@@ -1908,9 +1952,9 @@ paths:
         schema:
           example: my-dataspace
           type: string
-      - description: anchor-name
+      - description: source-anchor-name
         in: path
-        name: anchor-name
+        name: source-anchor-name
         required: true
         schema:
           example: my-anchor
@@ -1989,6 +2033,95 @@ paths:
       tags:
       - cps-data
       x-codegen-request-body-name: xpath
+    post:
+      description: Get delta between an anchor in a dataspace and JSON payload
+      operationId: getDeltaByDataspaceAnchorAndPayload
+      parameters:
+      - description: dataspace-name
+        in: path
+        name: dataspace-name
+        required: true
+        schema:
+          example: my-dataspace
+          type: string
+      - description: source-anchor-name
+        in: path
+        name: source-anchor-name
+        required: true
+        schema:
+          example: my-anchor
+          type: string
+      - description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html"
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+        in: query
+        name: xpath
+        required: false
+        schema:
+          default: /
+          type: string
+      requestBody:
+        content:
+          multipart/form-data:
+            schema:
+              $ref: '#/components/schemas/getDeltaByDataspaceAnchorAndPayload_request'
+      responses:
+        "200":
+          content:
+            application/json:
+              examples:
+                dataSample:
+                  $ref: '#/components/examples/deltaReportSample'
+                  value: null
+              schema:
+                type: object
+          description: OK
+        "400":
+          content:
+            application/json:
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+          description: Bad Request
+        "401":
+          content:
+            application/json:
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+          description: Unauthorized
+        "403":
+          content:
+            application/json:
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+          description: Forbidden
+        "500":
+          content:
+            application/json:
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+          description: Internal Server Error
+      summary: Get delta between an anchor and JSON payload
+      tags:
+      - cps-data
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
     get:
       deprecated: true
@@ -2270,7 +2403,7 @@ components:
     dataSampleXml:
       value: <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <bookstore xmlns="org:onap:ccsdk:sample">
         <bookstore-name>Chapters</bookstore-name> <categories> <code>1</code> <name>SciFi</name>
-        </categories> </bookstore> </stores>
+        <code>2</code> <name>kids</name> </categories> </bookstore> </stores>
     deltaReportSample:
       value:
       - action: ADD
@@ -2409,8 +2542,8 @@ components:
       schema:
         example: 2021-03-21T00:10:34.030-0100
         type: string
-    contentTypeHeader:
-      description: Content type header
+    contentTypeInHeader:
+      description: Content type in header
       in: header
       name: Content-Type
       required: true
@@ -2429,6 +2562,14 @@ components:
       required: true
       schema:
         type: string
+    sourceAnchorNameInPath:
+      description: source-anchor-name
+      in: path
+      name: source-anchor-name
+      required: true
+      schema:
+        example: my-anchor
+        type: string
     targetAnchorNameInQuery:
       description: target-anchor-name
       in: query
@@ -2471,7 +2612,7 @@ components:
   responses:
     Created:
       content:
-        text/plain:
+        application/json:
           schema:
             example: my-resource
             type: string
@@ -2530,6 +2671,16 @@ components:
           schema:
             type: object
       description: OK
+    Unauthorized:
+      content:
+        application/json:
+          example:
+            status: 401
+            message: Unauthorized request
+            details: This request is unauthorized
+          schema:
+            $ref: '#/components/schemas/ErrorMessage'
+      description: Unauthorized
   schemas:
     ErrorMessage:
       properties:
@@ -2619,6 +2770,24 @@ components:
           type: string
       title: Module reference object
       type: object
+    getDeltaByDataspaceAnchorAndPayload_request:
+      properties:
+        json:
+          example:
+            test:bookstore:
+              bookstore-name: Chapters
+              categories:
+              - code: 1
+                name: SciFi
+              - code: 2
+                name: kids
+          type: object
+        file:
+          format: binary
+          type: string
+      required:
+      - json
+      type: object
   securitySchemes:
     basicAuth:
       scheme: basic
index 4f5180d..d710316 100644 (file)
@@ -2,7 +2,7 @@ openapi: 3.0.3
 info:
   description: NCMP Inventory API
   title: NCMP Inventory API
-  version: "1.0"
+  version: 3.5.2
 servers:
 - url: /ncmpInventory
 security:
index 9f6a1b2..a227add 100644 (file)
@@ -2,7 +2,7 @@ openapi: 3.0.3
 info:
   description: NCMP to CPS Proxy API
   title: NCMP to CPS Proxy API
-  version: "1.0"
+  version: 3.5.2
 servers:
 - url: /ncmp
 security:
@@ -28,8 +28,7 @@ paths:
         schema:
           example: my-cm-handle
           type: string
-      - allowReserved: true
-        description: The format of resource identifier depend on the associated DMI
+      - description: The format of resource identifier depend on the associated DMI
           Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
           it can really be anything.
         examples:
@@ -139,8 +138,7 @@ paths:
         schema:
           example: my-cm-handle
           type: string
-      - allowReserved: true
-        description: The format of resource identifier depend on the associated DMI
+      - description: The format of resource identifier depend on the associated DMI
           Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
           it can really be anything.
         examples:
@@ -158,8 +156,7 @@ paths:
         required: true
         schema:
           type: string
-      - allowReserved: true
-        description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
+      - description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
           \ in parenthesis'()'. The format of options parameter depend on the associated\
           \ DMI Plugin implementation."
         examples:
@@ -177,8 +174,7 @@ paths:
         required: false
         schema:
           type: string
-      - allowReserved: true
-        description: topic parameter in query.
+      - description: topic parameter in query.
         examples:
           sample 1:
             value:
@@ -276,8 +272,7 @@ paths:
         schema:
           example: my-cm-handle
           type: string
-      - allowReserved: true
-        description: The format of resource identifier depend on the associated DMI
+      - description: The format of resource identifier depend on the associated DMI
           Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
           it can really be anything.
         examples:
@@ -390,8 +385,7 @@ paths:
         schema:
           example: my-cm-handle
           type: string
-      - allowReserved: true
-        description: The format of resource identifier depend on the associated DMI
+      - description: The format of resource identifier depend on the associated DMI
           Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
           it can really be anything.
         examples:
@@ -509,8 +503,7 @@ paths:
         schema:
           example: my-cm-handle
           type: string
-      - allowReserved: true
-        description: The format of resource identifier depend on the associated DMI
+      - description: The format of resource identifier depend on the associated DMI
           Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
           it can really be anything.
         examples:
@@ -615,12 +608,11 @@ paths:
     post:
       description: This request will be handled asynchronously using messaging to
         the supplied topic. The rest response will be an acknowledge with a requestId
-        to identify the relevant messages. A maximum of 50 cm handles per operation
+        to identify the relevant messages. A maximum of 200 cm handles per operation
         is supported.
       operationId: executeDataOperationForCmHandles
       parameters:
-      - allowReserved: true
-        description: mandatory topic parameter in query.
+      - description: mandatory topic parameter in query.
         examples:
           sample 1:
             value:
@@ -736,8 +728,7 @@ paths:
         schema:
           default: /
           type: string
-      - allowReserved: true
-        description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
+      - description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
           \ in parenthesis'()'. The format of options parameter depend on the associated\
           \ DMI Plugin implementation."
         examples:
@@ -755,8 +746,7 @@ paths:
         required: false
         schema:
           type: string
-      - allowReserved: true
-        description: topic parameter in query.
+      - description: topic parameter in query.
         examples:
           sample 1:
             value:
@@ -1484,7 +1474,6 @@ components:
         example: my-cm-handle
         type: string
     resourceIdentifierInQuery:
-      allowReserved: true
       description: The format of resource identifier depend on the associated DMI
         Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but it
         can really be anything.
@@ -1504,7 +1493,6 @@ components:
       schema:
         type: string
     optionsParamInQuery:
-      allowReserved: true
       description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
         \ in parenthesis'()'. The format of options parameter depend on the associated\
         \ DMI Plugin implementation."
@@ -1524,7 +1512,6 @@ components:
       schema:
         type: string
     topicParamInQuery:
-      allowReserved: true
       description: topic parameter in query.
       examples:
         sample 1:
@@ -1561,7 +1548,6 @@ components:
         example: application/yang-data+json
         type: string
     requiredTopicParamInQuery:
-      allowReserved: true
       description: mandatory topic parameter in query.
       examples:
         sample 1:
diff --git a/docs/api/swagger/policy-executor/openapi.yaml b/docs/api/swagger/policy-executor/openapi.yaml
new file mode 100644 (file)
index 0000000..6b73d98
--- /dev/null
@@ -0,0 +1,198 @@
+#  ============LICENSE_START=======================================================
+#  Copyright (C) 2024 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=========================================================
+
+openapi: 3.0.3
+info:
+  title: Policy Executor
+  description: "Allows NCMP to execute a policy defined by a third party implementation before proceeding with a CM operation"
+  version: 1.0.0
+tags:
+  - name: policy-executor
+    description: "Execute all your policies"
+paths:
+  /policy-executor/api/v1/{action}:
+    post:
+      description: "Fire a Policy action"
+      operationId: executePolicyAction
+      parameters:
+        - $ref: '#/components/parameters/authorizationInHeader'
+        - $ref: '#/components/parameters/actionInPath'
+      requestBody:
+        required: true
+        description: "The action request body"
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/PolicyExecutionRequest'
+      tags:
+        - policy-executor
+      responses:
+        '200':
+          description: "Successful policy execution"
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/PolicyExecutionResponse'
+        '400':
+          $ref: '#/components/responses/BadRequest'
+        '401':
+          $ref: '#/components/responses/Unauthorized'
+        '403':
+          $ref: '#/components/responses/Forbidden'
+        '500':
+          $ref: '#/components/responses/InternalServerError'
+
+components:
+  securitySchemes:
+    bearerAuth:
+      type: http
+      description: "Bearer token (from client that called CPS-NCMP),used by policies to identify the client"
+      scheme: bearer
+  schemas:
+    ErrorMessage:
+      type: object
+      title: Error
+      properties:
+        status:
+          type: string
+        message:
+          type: string
+        details:
+          type: string
+
+    Request:
+      type: object
+      properties:
+        schema:
+          type: string
+          description: "The schema for the data in this request. The schema name should include the type of operation"
+          example: "org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0"
+        data:
+          type: object
+          description: "The data related to the request. The format of the object is determined by the schema"
+      required:
+        - schema
+        - data
+
+    PolicyExecutionRequest:
+      type: object
+      properties:
+        decisionType:
+          type: string
+          description: "The type of decision. Currently supported options: 'allow'"
+          example: "allow"
+        requests:
+          type: array
+          items:
+            $ref: '#/components/schemas/Request'
+      required:
+        - decisionType
+        - requests
+
+    PolicyExecutionResponse:
+      type: object
+      properties:
+        decisionId:
+          type: string
+          description: "Unique ID for the decision (for auditing purposes)"
+          example: "550e8400-e29b-41d4-a716-446655440000"
+        decision:
+          type: string
+          description: "The decision outcome. Currently supported values: 'allow','deny'"
+          example: "deny"
+        message:
+          type: string
+          description: "Additional information regarding the decision outcome"
+          example: "Object locked due to recent change"
+      required:
+        - decisionId
+        - decision
+        - message
+
+  responses:
+    BadRequest:
+      description: "Bad request"
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/ErrorMessage'
+          example:
+            status: 400
+            message: "Bad Request"
+            details: "The provided request is not valid"
+    Unauthorized:
+      description: "Unauthorized request"
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/ErrorMessage'
+          example:
+            status: 401
+            message: "Unauthorized request"
+            details: "This request is unauthorized"
+    Forbidden:
+      description: "Request forbidden"
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/ErrorMessage'
+          example:
+            status: 403
+            message: "Request Forbidden"
+            details: "This request is forbidden"
+
+    InternalServerError:
+      description: "Internal server error"
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/ErrorMessage'
+          example:
+            status: 500
+            message: "Internal Server Error"
+            details: "Internal server error occurred"
+
+    NotImplemented:
+      description: "Method not (yet) implemented"
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/ErrorMessage'
+          example:
+            status: 501
+            message: "Not Implemented"
+            details: "Method not implemented"
+
+  parameters:
+    actionInPath:
+      name: action
+      in: path
+      description: "The policy action. Currently supported options: 'execute'"
+      required: true
+      schema:
+        type: string
+        example: "execute"
+    authorizationInHeader:
+      name: Authorization
+      in: header
+      description: "Bearer token may be used to identify client as part of a policy"
+      schema:
+        type: string
+
+security:
+  - bearerAuth: []
diff --git a/docs/cm-notification-subscriptions.rst b/docs/cm-notification-subscriptions.rst
new file mode 100644 (file)
index 0000000..14e871a
--- /dev/null
@@ -0,0 +1,48 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. Copyright (C) 2024 Nordix Foundation
+
+.. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
+.. _cmNotificationSubscriptions:
+
+
+CM Data Subscriptions
+#####################
+
+.. toctree::
+   :maxdepth: 1
+
+Introduction
+============
+CM Subscriptions are created to subscribe to notifications for CM related changes that happened in the network based on predicates.
+Predicates can be used to filter on CM Handle (id), Datastore and Xpath.
+
+The CM Subscription flow is event driven and adheres to the CNCF Cloud Events Specifications.
+
+Event to create and delete a subscription.
+
+:download:`CM Subscription Event Schema <schemas/ncmp-in-event-schema-1.0.0.json>`
+
+Event to receive status of participants in a subscription.
+
+:download:`CM Subscription Response Event Schema <schemas/ncmp-out-event-schema-1.0.0.json>`
+
+CM Subscriptions Creation
+-------------------------
+To create a subscription, a client sends an event to a configured topic to register its interest with NCMP allowing the client to receive notifications based on the subscription.
+
+CM Subscriptions Deletion
+-------------------------
+If a client no longer wishes to receive notifications based on a registered subscription, the client can delete the subscription by providing the subscription id.
+
+CM Subscriptions Response
+-------------------------
+The response for the involved subscription participants for the Create and Delete flow can be as follows based on how the DMI Plugin responds back to NCMP.
+    - **ACCEPTED:** DMI Plugin successfully applied the subscription.
+    - **REJECTED:** DMI Plugin failed to apply the subscription.
+    - **PENDING:** DMI Plugin failed to respond within a configured time.
+
+**Note.** The Cm Subscription feature relies on the DMI Plugin support for applying the subscriptions. This support is currently not implemented in the ONAP DMI Plugin.
+
+
+
index e8bb663..5d7a799 100755 (executable)
@@ -31,7 +31,7 @@ html_theme = "sphinx_rtd_theme"
 html_theme_options = {
     "style_nav_header_background": "white",
     "sticky_navigation": "False" }
-html_logo = "_static/logo_onap_2017.png"
+html_logo = "_static/logo_onap_2024.png"
 html_favicon = "_static/favicon.ico"
 html_static_path = ["_static"]
 html_show_sphinx = False
index b2e4e60..ecb7550 100644 (file)
@@ -38,7 +38,7 @@ Sample Delta Report
 
     [
       {
-        "action": "ADD",
+        "action": "create",
         "xpath": "/bookstore/categories/[@code=3]",
         "target-data": {
           "code": "3,",
@@ -46,7 +46,7 @@ Sample Delta Report
         }
       },
       {
-        "action": "REMOVE",
+        "action": "remove",
         "xpath": "/bookstore/categories/[@code=1]",
         "source-data": {
           "code": "1,",
@@ -54,7 +54,7 @@ Sample Delta Report
         }
       },
       {
-        "action": "UPDATE",
+        "action": "replace",
         "xpath": "/bookstore/categories/[@code=2]",
         "source-data": {
           "name": "Funny"
index f3a2f94..34aa43d 100644 (file)
@@ -22,10 +22,10 @@ Delta Report Format
 -------------------
 
 The Delta Report is based on the RFC 9144, which defines a set of parameters to be included in the Delta Report. In CPS only the relevant parameters defined in RFC 9144 are used. These include:
-    - **action:** This parameter defines how the data nodes changed between two configurations. If data nodes are added, deleted or modified then the 'action' parameter in the delta report will be set to ADD, DELETE or UPDATE respectively for each data node.
+    - **action:** This parameter defines how the data nodes changed between two configurations. If data nodes are added, deleted or modified then the 'action' parameter in the delta report will be set to 'create', 'remove' or 'replace' respectively for each data node.
     - **xpath:** This parameter will provide the xpath of each data node that has been either added, deleted or modified.
-    - **source-data:** this parameter is added to the delta report when a data node is deleted or updated, this represents the source/reference data against which the delta is being generated. In case of newly added data node this field is not included in the delta report.
-    - **target-data:** this parameter is added to the delta report when a data node is added or updated, this represents the data values that are being compared to the source data. In case of a data node being deleted this field is not included in the delta report.
+    - **source-data:** this parameter is added to the delta report when a data node is removed or updated, this represents the source/reference data against which the delta is being generated. In case of newly added data node this field is not included in the delta report.
+    - **target-data:** this parameter is added to the delta report when a data node is added or updated, this represents the data values that are being compared to the source data. In case of a data node being removed this field is not included in the delta report.
 
 **Note.** In case of an existing data node being modified, both the source-data and target-data fields are present in the delta report.
 
index 25a253b..47aa73f 100644 (file)
@@ -13,6 +13,7 @@ CPS Events
 
    cm-handle-lcm-events.rst
    data-operation-events.rst
+   cm-notification-subscriptions.rst
 
 .. note::
     Legacy async response on a client supplied topic for single cm handle data request are no longer supported. Click link below for the legacy specification.
index 0c6ce0e..e0a3f03 100644 (file)
@@ -14,7 +14,7 @@ CPS-NCMP Message Status Codes
     +=================+======================================================+===================================+
     | 0               | Successfully applied changes                         | Data Operation                    |
     +-----------------+------------------------------------------------------+-----------------------------------+
-    | 1               | successfully applied subscription                    | CM Data Notification Subscription |
+    | 1               | ACCEPTED                                             | CM Data Notification Subscription |
     +-----------------+------------------------------------------------------+-----------------------------------+
     | 100             | cm handle id(s) is(are) not found                    | All features                      |
     +-----------------+------------------------------------------------------+-----------------------------------+
@@ -24,11 +24,7 @@ CPS-NCMP Message Status Codes
     +-----------------+------------------------------------------------------+-----------------------------------+
     | 103             | dmi plugin service is not able to read resource data | Data Operation                    |
     +-----------------+------------------------------------------------------+-----------------------------------+
-    | 104             | partially applied subscription                       | CM Data Notification Subscription |
-    +-----------------+------------------------------------------------------+-----------------------------------+
-    | 105             | subscription not applicable for all cm handles       | CM Data Notification Subscription |
-    +-----------------+------------------------------------------------------+-----------------------------------+
-    | 106             | subscription pending for all cm handles              | CM Data Notification Subscription |
+    | 104             | REJECTED                                             | CM Data Notification Subscription |
     +-----------------+------------------------------------------------------+-----------------------------------+
     | 107             | southbound system is busy                            | Data Operation                    |
     +-----------------+------------------------------------------------------+-----------------------------------+
index af1f5ab..52f977a 100644 (file)
@@ -60,11 +60,20 @@ and CPS-NCMP-Inventory using the drop down table in the top right:
 Consumed APIs
 =============
 
-CPS Core uses API's from the following ONAP components
+DMI-Plugin
+----------
 
-* DMI-Plugin: REST based interface which is used to provide integration
-  and allow the DMI registry API's have access to the corresponding NCMP API's within CPS Core.
-  More information on the DMI-Plugins offered APIs can be found on the :ref:`DMI-Plugin's Design Page <onap-cps-ncmp-dmi-plugin:design>`.
+DMI-Plugin is a REST based interface which is used to provide integration
+and allow the DMI registry API's have access to the corresponding NCMP API's within CPS Core.
+More information on the DMI-Plugins offered APIs can be found on the :ref:`DMI-Plugin's Design Page <onap-cps-ncmp-dmi-plugin:design>`.
+
+Policy-Executor
+---------------
+
+.. toctree::
+   :maxdepth: 1
+
+   policy-executor.rst
 
 CPS Path
 ========
index 349b984..084eaaf 100644 (file)
@@ -156,3 +156,23 @@ With the *cmHandleWithDmiPlugin* condition, we can provide a dmiPluginName. The
         }
       ]
     }
+
+CM Handle search with CPS Path
+------------------------------
+
+The *cmHandleWithCpsPath* condition allows any data of the CM Handle to be queried as long as it is accessible by CPS path. CPS path is described in detail in :doc:`cps-path`. For this endpoint, the ancestor axis for CM Handles is appended automatically so that a CM Handle is always returned. For example ``/dmi-registry/cm-handles[@module-set-tag='']`` will become ``/dmi-registry/cm-handles[@module-set-tag='']/ancestor::cm-handles``.
+
+.. code-block:: json
+
+    {
+      "cmHandleQueryParameters": [
+        {
+          "conditionName": "cmHandleWithCpsPath",
+          "conditionParameters": [
+            {
+              "cpsPath": "/dmi-registry/cm-handles[@module-set-tag='some-value or empty']"
+            }
+          ]
+        }
+      ]
+    }
\ No newline at end of file
diff --git a/docs/policy-executor.rst b/docs/policy-executor.rst
new file mode 100644 (file)
index 0000000..b934a57
--- /dev/null
@@ -0,0 +1,23 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. Copyright (C) 2024 Nordix Foundation
+
+.. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
+.. _policy_executor:
+
+
+Policy Executor
+###############
+
+.. toctree::
+   :maxdepth: 1
+
+Introduction
+============
+
+Work In Progress: This feature is not yet completed and does not affect current NCMP functionality.
+
+Consumed APIs
+-------------
+
+:download:`Policy Executor OpenApi Specification <api/swagger/policy-executor/openapi.yaml>`
index 82a890d..9b23fbc 100644 (file)
@@ -12,12 +12,12 @@ CPS Release Notes
     :depth: 2
 ..
 
-..      =========================
-..      * * *   NEW DELHI   * * *
-..      =========================
+..      ====================
+..      * * *   OSLO   * * *
+..      ====================
 
-Version: 3.4.10
-===============
+Version: 3.5.3
+==============
 
 Release Data
 ------------
@@ -26,10 +26,10 @@ Release Data
 | **CPS Project**                      |                                                        |
 |                                      |                                                        |
 +--------------------------------------+--------------------------------------------------------+
-| **Docker images**                    | onap/cps-and-ncmp:3.4.10                               |
+| **Docker images**                    | onap/cps-and-ncmp:3.5.3                                |
 |                                      |                                                        |
 +--------------------------------------+--------------------------------------------------------+
-| **Release designation**              | 3.4.10 New Delhi                                       |
+| **Release designation**              | 3.5.3 Oslo                                             |
 |                                      |                                                        |
 +--------------------------------------+--------------------------------------------------------+
 | **Release date**                     | Not yet released                                       |
@@ -38,10 +38,112 @@ Release Data
 
 Bug Fixes
 ---------
-3.4.10
+3.5.3
+
+Features
+--------
+3.5.3
+
+
+Version: 3.5.2
+==============
+
+Release Data
+------------
+
++--------------------------------------+--------------------------------------------------------+
+| **CPS Project**                      |                                                        |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Docker images**                    | onap/cps-and-ncmp:3.5.2                                |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release designation**              | 3.5.2 Oslo                                             |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release date**                     | 2024 August 21                                         |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+
+Bug Fixes
+---------
+3.5.2
+    - `CPS-2306 <https://jira.onap.org/browse/CPS-2306>`_ Update response message for data validation failure and make it consistent across APIs
+    - `CPS-2319 <https://jira.onap.org/browse/CPS-2319>`_ Fix "Create a node" and "Add List Elements" APIs response code
+    - `CPS-2372 <https://jira.onap.org/browse/CPS-2372>`_ Blank alternate ID overwrites existing one
+
+Features
+--------
+3.5.2
+    - `CPS-1812 <https://jira.onap.org/browse/CPS-1812>`_ CM Data Subscriptions ( Create, Delete and Merging ) with positive scenarios
+    - `CPS-2326 <https://jira.onap.org/browse/CPS-2326>`_ Uplift liquibase-core dependency to 4.28.0
+    - `CPS-2353 <https://jira.onap.org/browse/CPS-2353>`_ Improve registration performance with moduleSetTag
+    - `CPS-2366 <https://jira.onap.org/browse/CPS-2366>`_ Improve registration performance with use of alternateID
+
+Version: 3.5.1
+==============
+
+Release Data
+------------
+
++--------------------------------------+--------------------------------------------------------+
+| **CPS Project**                      |                                                        |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Docker images**                    | onap/cps-and-ncmp:3.5.1                                |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release designation**              | 3.5.1 Oslo                                             |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release date**                     | 2024 July 15                                           |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+
+Bug Fixes
+---------
+3.5.1
+    - `CPS-2302 <https://jira.onap.org/browse/CPS-2302>`_ Fix handling of special characters in moduleSetTag.
 
 Features
 --------
+3.5.1
+    - `CPS-2121 <https://jira.onap.org/browse/CPS-2121>`_ Enabled http client prometheus metrics and manage high cardinality using URL template.
+    - `CPS-2289 <https://jira.onap.org/browse/CPS-2289>`_ Support for CPS Path Query in NCMP Inventory Cm Handle Search.
+
+Version: 3.5.0
+==============
+
+Release Data
+------------
+
++--------------------------------------+--------------------------------------------------------+
+| **CPS Project**                      |                                                        |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Docker images**                    | onap/cps-and-ncmp:3.5.0                                |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release designation**              | 3.5.0 Oslo                                             |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release date**                     | 2024 June 20                                           |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+
+Bug Fixes
+---------
+3.5.0
+
+Features
+--------
+3.5.0
+    - `CPS-989 <https://jira.onap.org/browse/CPS-989>`_ Replace RestTemplate with WebClient.
+    - `CPS-2172 <https://jira.onap.org/browse/CPS-2172>`_ Support for OpenTelemetry Tracing.
+
+..      =========================
+..      * * *   NEW DELHI   * * *
+..      =========================
 
 Version: 3.4.9
 ==============
@@ -70,6 +172,8 @@ Bug Fixes
 
 Features
 --------
+3.4.9
+    - `CPS-1836 <https://jira.onap.org/browse/CPS-1836>`_ Delta between anchor and JSON payload.
 
 Version: 3.4.8
 ==============
diff --git a/docs/schemas/ncmp-in-event-schema-1.0.0.json b/docs/schemas/ncmp-in-event-schema-1.0.0.json
new file mode 100644 (file)
index 0000000..f8b6c2e
--- /dev/null
@@ -0,0 +1,73 @@
+{
+  "$id": "urn:cps:org.onap.cps.ncmp.events:cm-notification-subscription-ncmp-in-event:1.0.0",
+  "$ref": "#/definitions/NcmpInEvent",
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "definitions": {
+    "NcmpInEvent": {
+      "description": "The payload for subscription merge event.",
+      "javaType": "org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.client_to_ncmp.NcmpInEvent",
+      "properties": {
+        "data": {
+          "properties": {
+            "subscriptionId": {
+              "description": "The subscription details.",
+              "type": "string"
+            },
+            "predicates": {
+              "type": "array",
+              "description": "Additional values to be added into the subscription",
+              "items": {
+                "type": "object",
+                "properties": {
+                  "targetFilter": {
+                    "description": "CM Handles to be targeted by the subscription",
+                    "type": "array",
+                    "items": {
+                      "type": "string"
+                    }
+                  },
+                  "scopeFilter": {
+                    "type": "object",
+                    "properties": {
+                      "datastore": {
+                        "description": "Datastore which is to be used by the subscription",
+                        "type": "string",
+                        "enum": ["ncmp-datastore:passthrough-operational", "ncmp-datastore:passthrough-running"]
+                      },
+                      "xpathFilter": {
+                        "description": "Filter to be applied to the CM Handles through this event",
+                        "type": "array",
+                        "items": {
+                          "type": "string"
+                        }
+                      }
+                    },
+                    "additionalProperties": false,
+                    "required": [
+                      "xpathFilter"
+                    ]
+                  }
+                },
+                "additionalProperties": false,
+                "required": [
+                  "targetFilter"
+                ]
+              },
+              "additionalProperties": false
+            }
+          },
+          "required": [
+            "subscriptionId"
+          ],
+          "type": "object",
+          "additionalProperties": false
+        }
+      },
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "data"
+      ]
+    }
+  }
+}
\ No newline at end of file
@@ -1,12 +1,12 @@
 {
   "$schema": "https://json-schema.org/draft/2019-09/schema",
   "$id": "urn:cps:org.onap.cps.ncmp.events:cm-notification-subscription-ncmp-out-event-schema:1.0.0",
-  "$ref": "#/definitions/CmNotificationSubscriptionNcmpOutEvent",
+  "$ref": "#/definitions/NcmpOutEvent",
   "definitions": {
-    "CmNotificationSubscriptionNcmpOutEvent": {
+    "NcmpOutEvent": {
       "type": "object",
       "description": "The payload applied cm subscription merge event coming out from NCMP.",
-      "javaType": "org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent",
+      "javaType": "org.onap.cps.ncmp.impl.cmnotificationsubscription_1_0_0.ncmp_to_client.NcmpOutEvent",
       "additionalProperties": false,
       "properties": {
         "data": {
@@ -16,7 +16,7 @@
       "required": [
         "data"
       ],
-      "title": "CmNotificationSubscriptionNcmpOutEvent"
+      "title": "NcmpOutEvent"
     },
     "Data": {
       "type": "object",
           "type": "string",
           "description": "The unique subscription id"
         },
-        "accepted-targets": {
+        "acceptedTargets": {
           "type": "array",
           "description": "List of accepted targets",
           "items": {
             "type": "string"
           }
         },
-        "rejected-targets": {
+        "rejectedTargets": {
           "type": "array",
           "description": "List of rejected targets",
           "items": {
             "type": "string"
           }
         },
-        "pending-targets": {
+        "pendingTargets": {
           "type": "array",
           "description": "List of pending targets",
           "items": {
         }
       },
       "required": [
-        "accepted-targets",
-        "pending-targets",
-        "rejected-targets",
-        "subscriptionId"
+        "subscriptionId",
+        "acceptedTargets",
+        "rejectedTargets",
+        "pendingTargets"
       ],
       "title": "Data"
     }
diff --git a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json
new file mode 100644 (file)
index 0000000..4d98bc8
--- /dev/null
@@ -0,0 +1,29 @@
+{
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-create-schema:1.0.0",
+  "$ref": "#/definitions/NcmpCreate",
+  "definitions": {
+    "NcmpCreate": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "cmHandleId": {
+          "type": "string"
+        },
+        "resourceIdentifier": {
+          "type": "string"
+        },
+        "targetIdentifier": {
+          "type": "string"
+        },
+        "cmChangeRequest": {
+          "type": "object"
+        }
+      },
+      "required": [
+        "targetIdentifier",
+        "cmChangeRequest"
+      ]
+    }
+  }
+}
diff --git a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json
new file mode 100644 (file)
index 0000000..1246d9d
--- /dev/null
@@ -0,0 +1,25 @@
+{
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-delete-schema:1.0.0",
+  "$ref": "#/definitions/NcmpDelete",
+  "definitions": {
+    "NcmpDelete": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "cmHandleId": {
+          "type": "string"
+        },
+        "resourceIdentifier": {
+          "type": "string"
+        },
+        "targetIdentifier": {
+          "type": "string"
+        }
+      },
+      "required": [
+        "targetIdentifier"
+      ]
+    }
+  }
+}
diff --git a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json
new file mode 100644 (file)
index 0000000..4917aea
--- /dev/null
@@ -0,0 +1,29 @@
+{
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-patch-schema:1.0.0",
+  "$ref": "#/definitions/NcmpPatch",
+  "definitions": {
+    "NcmpPatch": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "cmHandleId": {
+          "type": "string"
+        },
+        "resourceIdentifier": {
+          "type": "string"
+        },
+        "targetIdentifier": {
+          "type": "string"
+        },
+        "cmChangeRequest": {
+          "type": "object"
+        }
+      },
+      "required": [
+        "targetIdentifier",
+        "cmChangeRequest"
+      ]
+    }
+  }
+}
diff --git a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json
new file mode 100644 (file)
index 0000000..831526c
--- /dev/null
@@ -0,0 +1,29 @@
+{
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-update-schema:1.0.0",
+  "$ref": "#/definitions/NcmpUpdate",
+  "definitions": {
+    "NcmpUpdate": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "cmHandleId": {
+          "type": "string"
+        },
+        "resourceIdentifier": {
+          "type": "string"
+        },
+        "targetIdentifier": {
+          "type": "string"
+        },
+        "cmChangeRequest": {
+          "type": "object"
+        }
+      },
+      "required": [
+        "targetIdentifier",
+        "cmChangeRequest"
+      ]
+    }
+  }
+}
index f228e01..ef8fdc8 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
             <artifactId>spock</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-json</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
index 6952b2a..587cbae 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.integration.base
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
-
-import java.time.OffsetDateTime
-import java.time.format.DateTimeFormatter
 import okhttp3.mockwebserver.MockWebServer
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
 import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsDataspaceService
@@ -35,19 +29,24 @@ import org.onap.cps.api.CpsModuleService
 import org.onap.cps.api.CpsQueryService
 import org.onap.cps.integration.DatabaseTestContainer
 import org.onap.cps.integration.KafkaTestContainer
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncWatchdog
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
+import org.onap.cps.ncmp.impl.data.NetworkCmProxyQueryService
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+import org.onap.cps.ri.repository.DataspaceRepository
+import org.onap.cps.ri.utils.SessionManager
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 import org.onap.cps.spi.model.DataNode
-import org.onap.cps.spi.repository.DataspaceRepository
-import org.onap.cps.spi.utils.SessionManager
+import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration
 import org.springframework.boot.autoconfigure.domain.EntityScan
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
@@ -60,13 +59,20 @@ import spock.lang.Shared
 import spock.lang.Specification
 import spock.util.concurrent.PollingConditions
 
+import java.time.OffsetDateTime
+import java.time.format.DateTimeFormatter
+
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
+
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = [CpsDataspaceService])
 @Testcontainers
 @EnableAutoConfiguration
 @AutoConfigureMockMvc
 @EnableJpaRepositories(basePackageClasses = [DataspaceRepository])
 @ComponentScan(basePackages = ['org.onap.cps'])
-@EntityScan('org.onap.cps.spi.entities')
+@EntityScan('org.onap.cps.ri.models')
 abstract class CpsIntegrationSpecBase extends Specification {
 
     @Shared
@@ -97,10 +103,13 @@ abstract class CpsIntegrationSpecBase extends Specification {
     SessionManager sessionManager
 
     @Autowired
-    NetworkCmProxyCmHandleQueryService networkCmProxyCmHandleQueryService
+    ParameterizedCmHandleQueryService networkCmProxyCmHandleQueryService
+
+    @Autowired
+    NetworkCmProxyFacade networkCmProxyFacade
 
     @Autowired
-    NetworkCmProxyDataService networkCmProxyDataService
+    NetworkCmProxyInventoryFacade NetworkCmProxyInventoryFacade
 
     @Autowired
     NetworkCmProxyQueryService networkCmProxyQueryService
@@ -114,16 +123,32 @@ abstract class CpsIntegrationSpecBase extends Specification {
     @Autowired
     InventoryPersistence inventoryPersistence
 
-    MockWebServer mockDmiServer = null
-    DmiDispatcher dmiDispatcher = new DmiDispatcher()
+    @Autowired
+    AlternateIdMatcher alternateIdMatcher
+
 
-    def DMI_URL = null
+    @Value('${ncmp.policy-executor.server.port:8080}')
+    private String policyServerPort;
+
+    MockWebServer mockDmiServer1 = new MockWebServer()
+    MockWebServer mockDmiServer2 = new MockWebServer()
+    MockWebServer mockPolicyServer = new MockWebServer()
+
+    DmiDispatcher dmiDispatcher1 = new DmiDispatcher()
+    DmiDispatcher dmiDispatcher2 = new DmiDispatcher()
+
+    PolicyDispatcher policyDispatcher = new PolicyDispatcher();
+
+    def DMI1_URL = null
+    def DMI2_URL = null
 
     static NO_MODULE_SET_TAG = ''
+    static NO_ALTERNATE_ID = ''
     static GENERAL_TEST_DATASPACE = 'generalTestDataspace'
     static BOOKSTORE_SCHEMA_SET = 'bookstoreSchemaSet'
+    static MODULE_SYNC_WAIT_TIME_IN_SECONDS = 10
 
-    def static initialized = false
+    static initialized = false
     def now = OffsetDateTime.now()
 
     def setup() {
@@ -132,14 +157,24 @@ abstract class CpsIntegrationSpecBase extends Specification {
             createStandardBookStoreSchemaSet(GENERAL_TEST_DATASPACE)
             initialized = true
         }
-        mockDmiServer = new MockWebServer()
-        mockDmiServer.setDispatcher(dmiDispatcher)
-        mockDmiServer.start()
-        DMI_URL = String.format("http://%s:%s", mockDmiServer.getHostName(), mockDmiServer.getPort())
+        mockDmiServer1.setDispatcher(dmiDispatcher1)
+        mockDmiServer1.start()
+
+        mockDmiServer2.setDispatcher(dmiDispatcher2)
+        mockDmiServer2.start()
+
+        mockPolicyServer.setDispatcher(policyDispatcher)
+        mockPolicyServer.start(Integer.valueOf(policyServerPort))
+
+        DMI1_URL = String.format("http://%s:%s", mockDmiServer1.getHostName(), mockDmiServer1.getPort())
+        DMI2_URL = String.format("http://%s:%s", mockDmiServer2.getHostName(), mockDmiServer2.getPort())
+
     }
 
     def cleanup() {
-        mockDmiServer.shutdown()
+        mockDmiServer1.shutdown()
+        mockDmiServer2.shutdown()
+        mockPolicyServer.shutdown()
     }
 
     def static readResourceDataFile(filename) {
@@ -205,11 +240,14 @@ abstract class CpsIntegrationSpecBase extends Specification {
     // *** NCMP Integration Test Utilities ***
 
     def registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag) {
-        def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag)
-        networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate]))
-        moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
-        new PollingConditions().within(3, () -> {
-            CmHandleState.READY == networkCmProxyDataService.getCmHandleCompositeState(cmHandleId).cmHandleState
+        registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag, NO_ALTERNATE_ID)
+    }
+
+    def registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag, alternateId) {
+        def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag, alternateId: alternateId)
+        networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate]))
+        new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
+            CmHandleState.READY == networkCmProxyInventoryFacade.getCmHandleCompositeState(cmHandleId).cmHandleState
         })
     }
 
@@ -218,7 +256,7 @@ abstract class CpsIntegrationSpecBase extends Specification {
     }
 
     def deregisterCmHandles(dmiPlugin, cmHandleIds) {
-        networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds))
+        networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds))
     }
 
     def overrideCmHandleLastUpdateTime(cmHandleId, newUpdateTime) {
@@ -226,6 +264,6 @@ abstract class CpsIntegrationSpecBase extends Specification {
         DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN);
         def jsonForUpdate = '{ "state": { "last-update-time": "%s" } }'.formatted(ISO_TIMESTAMP_FORMATTER.format(newUpdateTime))
         cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='${cmHandleId}']", jsonForUpdate, now)
+                NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='${cmHandleId}']", jsonForUpdate, now, ContentType.JSON)
     }
 }
index 6676cb7..35a7b6a 100644 (file)
 
 package org.onap.cps.integration.base
 
-import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile
-
-import org.springframework.http.HttpHeaders
-import java.util.regex.Matcher
+import groovy.json.JsonSlurper
 import okhttp3.mockwebserver.Dispatcher
 import okhttp3.mockwebserver.MockResponse
 import okhttp3.mockwebserver.RecordedRequest
+import org.springframework.http.HttpHeaders
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
 
+import java.util.regex.Matcher
+
+import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile
+
 /**
  * This class simulates responses from the DMI server in NCMP integration tests.
  *
@@ -54,51 +56,95 @@ class DmiDispatcher extends Dispatcher {
 
     def isAvailable = true
 
-    Map<String, List<String>> moduleNamesPerCmHandleId = [:]
+    def jsonSlurper = new JsonSlurper()
+    def moduleNamesPerCmHandleId = [:]
+    def receivedSubJobs = [:]
+    def lastAuthHeaderReceived
+    def dmiResourceDataUrl
 
     @Override
     MockResponse dispatch(RecordedRequest request) {
         if (!isAvailable) {
-            return new MockResponse().setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.value())
+            return mockResponse(HttpStatus.SERVICE_UNAVAILABLE)
+        }
+        if (request.path == '/actuator/health') {
+            return mockResponseWithBody(HttpStatus.OK, '{"status":"UP"}')
         }
+
+        lastAuthHeaderReceived = request.getHeader('Authorization')
         switch (request.path) {
-            case ~/^\/dmi\/v1\/ch\/(.*)\/modules$/:
+            // get module references for a CM-handle
+            case ~'^/dmi/v1/ch/(.*)/modules$':
                 def cmHandleId = Matcher.lastMatcher[0][1]
                 return getModuleReferencesResponse(cmHandleId)
 
-            case ~/^\/dmi\/v1\/ch\/(.*)\/moduleResources$/:
+            // get module resources for a CM-handle
+            case ~'^/dmi/v1/ch/(.*)/moduleResources$':
                 def cmHandleId = Matcher.lastMatcher[0][1]
                 return getModuleResourcesResponse(cmHandleId)
 
+            // pass-through data operation for a CM-handle
+            case ~'^/dmi/v1/ch/(.*)/data/ds/(.*)$':
+                dmiResourceDataUrl = request.path
+                return mockResponseWithBody(HttpStatus.OK, '{}')
+
+            // legacy pass-through batch data operation
+            case ~'^/dmi/v1/data$':
+                return mockResponseWithBody(HttpStatus.ACCEPTED, '{}')
+
+            // get data job status
+            case ~'^/dmi/v1/cmwriteJob/dataProducer/(.*)/dataProducerJob/(.*)/status$':
+                return mockResponseWithBody(HttpStatus.OK, '{"status":"status details from mock service"}')
+
+            // get data job result
+            case ~'^/dmi/v1/cmwriteJob/dataProducer/(.*)/dataProducerJob/(.*)/result(.*)$':
+                return mockResponseWithBody(HttpStatus.OK, '{ "result": "some result"}')
+
+            // get write sub job response
+            case ~'^/dmi/v1/cmwriteJob(.*)$':
+                return mockWriteJobResponse(request)
+
             default:
-                throw new IllegalArgumentException('Mock DMI does not handle path ' + request.path)
+                throw new IllegalArgumentException('Mock DMI does not implement endpoint ' + request.path)
         }
     }
 
-    private getModuleReferencesResponse(cmHandleId) {
+    def mockWriteJobResponse(request) {
+        def destination = Matcher.lastMatcher[0][1]
+        def subJobWriteRequest = jsonSlurper.parseText(request.getBody().readUtf8())
+        this.receivedSubJobs.put(destination, subJobWriteRequest)
+        def response = '{"subJobId":"some sub job id", "dmiServiceName":"some dmi service name", "dataProducerId":"some data producer id"}'
+        return mockResponseWithBody(HttpStatus.OK, response)
+    }
+
+    def getModuleReferencesResponse(cmHandleId) {
         def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect {
             MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it)
         }.join(',') + ']}'
-        return mockOkResponseWithBody(moduleReferences)
+        return mockResponseWithBody(HttpStatus.OK, moduleReferences)
     }
 
-    private getModuleResourcesResponse(cmHandleId) {
+    def getModuleResourcesResponse(cmHandleId) {
         def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect {
             MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it)
         }.join(',') + ']'
-        return mockOkResponseWithBody(moduleResources)
+        return mockResponseWithBody(HttpStatus.OK, moduleResources)
     }
 
-    private getModuleNamesForCmHandle(cmHandleId) {
+    def getModuleNamesForCmHandle(cmHandleId) {
         if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) {
             throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId)
         }
         return moduleNamesPerCmHandleId.get(cmHandleId)
     }
 
-    private static mockOkResponseWithBody(responseBody) {
+    def static mockResponse(status) {
+        return new MockResponse().setResponseCode(status.value())
+    }
+
+    def static mockResponseWithBody(status, responseBody) {
         return new MockResponse()
-                .setResponseCode(HttpStatus.OK.value())
+                .setResponseCode(status.value())
                 .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
                 .setBody(responseBody)
     }
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy
new file mode 100644 (file)
index 0000000..69792d7
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration.base
+
+
+import okhttp3.mockwebserver.Dispatcher
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.RecordedRequest
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper
+
+/**
+ * This class simulates responses from the Policy Execution server in NCMP integration tests.
+ */
+class PolicyDispatcher extends Dispatcher {
+
+    def objectMapper = new ObjectMapper()
+    def expectedAuthorizationToken = 'ABC'
+    def allowAll = true; // Prevents legacy test being affected
+
+    @Override
+    MockResponse dispatch(RecordedRequest recordedRequest) {
+
+        if (!allowAll && !recordedRequest.getHeader('Authorization').contains(expectedAuthorizationToken)) {
+            return new MockResponse().setResponseCode(401)
+        }
+
+        if (recordedRequest.path != '/policy-executor/api/v1/execute') {
+            return new MockResponse().setResponseCode(400)
+        }
+
+        def body = objectMapper.readValue(recordedRequest.getBody().readUtf8(), Map.class)
+        def targetIdentifier = body.get('requests').get(0).get('data').get('targetIdentifier')
+        def responseAsMap = [:]
+        responseAsMap.put('decisionId',1)
+        if (allowAll || targetIdentifier == 'fdn1') {
+            responseAsMap.put('decision','allow')
+            responseAsMap.put('message','')
+        } else {
+            responseAsMap.put('decision','deny')
+            responseAsMap.put('message','I only like fdn1')
+        }
+        def responseAsString = objectMapper.writeValueAsString(responseAsMap)
+
+        return mockResponseWithBody(HttpStatus.OK, responseAsString)
+    }
+
+    static mockResponseWithBody(status, responseBody) {
+        return new MockResponse()
+                .setResponseCode(status.value())
+                .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+                .setBody(responseBody)
+    }
+}
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.cps
 
 import java.time.OffsetDateTime
 
 import org.onap.cps.api.CpsAnchorService
-import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.integration.base.FunctionalSpecBase
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.AnchorNotFoundException
+import org.onap.cps.utils.ContentType
 
-class CpsAnchorServiceIntegrationSpec extends CpsIntegrationSpecBase {
+class AnchorServiceIntegrationSpec extends FunctionalSpecBase {
 
     CpsAnchorService objectUnderTest
 
@@ -61,9 +63,9 @@ class CpsAnchorServiceIntegrationSpec extends CpsIntegrationSpecBase {
         then: 'there are 3 anchors in the general test database'
             assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE).size() == 3
         and: 'there are 2 anchors associated with bookstore schema set'
-            assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET).size() == 2
+            assert objectUnderTest.getAnchorsBySchemaSetName(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET).size() == 2
         and: 'there is 1 anchor associated with other schema set'
-            assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE, 'otherSchemaSet').size() == 1
+            assert objectUnderTest.getAnchorsBySchemaSetName(GENERAL_TEST_DATASPACE, 'otherSchemaSet').size() == 1
     }
 
     def 'Querying anchor(name)s (depends on previous test!).'() {
@@ -110,7 +112,7 @@ class CpsAnchorServiceIntegrationSpec extends CpsIntegrationSpecBase {
             objectUnderTest.updateAnchorSchemaSet(GENERAL_TEST_DATASPACE, 'anchor4', 'anotherTreeSchemaSet')
         when: 'updated tree data node with new leaves'
             def updatedTreeJsonData = readResourceDataFile('tree/updated-test-tree.json')
-            cpsDataService.updateNodeLeaves(GENERAL_TEST_DATASPACE, "anchor4", "/test-tree/branch[@name='left']", updatedTreeJsonData, OffsetDateTime.now())
+            cpsDataService.updateNodeLeaves(GENERAL_TEST_DATASPACE, "anchor4", "/test-tree/branch[@name='left']", updatedTreeJsonData, OffsetDateTime.now(), ContentType.JSON)
         then: 'updated tree data node can be retrieved by its normalized xpath'
             def birdsName = cpsDataService.getDataNodes(GENERAL_TEST_DATASPACE, 'anchor4',"/test-tree/branch[@name='left']/nest", FetchDescendantsOption.DIRECT_CHILDREN_ONLY)[0].leaves['birds'] as List
             assert birdsName.size() == 3
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2023-2024 Nordix Foundation
- *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2023-2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.cps
 
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.integration.base.FunctionalSpecBase
@@ -33,12 +33,13 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 import org.onap.cps.spi.model.DeltaReport
+import org.onap.cps.utils.ContentType
 
 import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
 
-class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
+class DataServiceIntegrationSpec extends FunctionalSpecBase {
 
     CpsDataService objectUnderTest
     def originalCountBookstoreChildNodes
@@ -223,7 +224,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'a new (multiple-data-tree:invoice) datanodes'
             def json = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Bangalore,India","postal-code":"560043"}]}'
         when: 'the new list elements are saved'
-            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now)
+            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now, ContentType.JSON)
         then: 'they can be retrieved by their xpaths'
             objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore-address[@bookstore-name="Easons"]', INCLUDE_ALL_DESCENDANTS)
         and: 'there is one extra datanode'
@@ -238,7 +239,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'two new (categories) data nodes'
             def json = '{"categories": [ {"code":"new1"}, {"code":"new2" } ] }'
         when: 'the new list elements are saved'
-            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now)
+            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON)
         then: 'they can be retrieved by their xpaths'
             objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', DIRECT_CHILDREN_ONLY).size() == 1
             objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new2"]', DIRECT_CHILDREN_ONLY).size() == 1
@@ -255,7 +256,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'two (categories) data nodes, one new and one existing'
             def json = '{"categories": [ {"code":"1"}, {"code":"new1"} ] }'
         when: 'attempt to save the list element'
-            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now)
+            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON)
         then: 'an exception that (one cps paths is)  already defined is thrown '
             def exceptionThrown = thrown(AlreadyDefinedException)
             exceptionThrown.alreadyDefinedObjectNames == ['/bookstore/categories[@code=\'1\']' ] as Set
@@ -269,7 +270,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'a new (categories) data nodes'
             def json = '{"categories": [ {"code":"new1"} ] }'
         and: 'the new list element is saved'
-            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now)
+            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON)
         when: 'the new element is deleted'
             objectUnderTest.deleteListOrListElement(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', now)
         then: 'the original number of data nodes is restored'
@@ -280,7 +281,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'two new (categories) data nodes in a single batch'
             def json = '{"categories": [ {"code":"new1"}, {"code":"new2"} ] }'
         when: 'the batches of new list element(s) are saved'
-            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now)
+            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON)
         then: 'they can be retrieved by their xpaths'
             assert objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', DIRECT_CHILDREN_ONLY).size() == 1
             assert objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new2"]', DIRECT_CHILDREN_ONLY).size() == 1
@@ -297,7 +298,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'one existing and one new (categories) data nodes in a single batch'
             def json = '{"categories": [ {"code":"new1"}, {"code":"1"} ] }'
         when: 'the batches of new list element(s) are saved'
-            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now)
+            objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON)
         then: 'an already defined (batch) exception is thrown for the existing path'
             def exceptionThrown = thrown(AlreadyDefinedException)
             assert exceptionThrown.alreadyDefinedObjectNames ==  ['/bookstore/categories[@code=\'1\']' ] as Set
@@ -366,7 +367,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
             objectUnderTest.saveData(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now)
         when: 'update is performed to add a leaf'
             def updatedJson = '{"webinfo": {"domain-name":"new leaf data"}}'
-            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, "/bookstore", updatedJson, now)
+            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, "/bookstore", updatedJson, now, ContentType.JSON)
         then: 'the updated data nodes are retrieved'
             def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, "/bookstore/webinfo", INCLUDE_ALL_DESCENDANTS)
         and: 'the leaf value is updated as expected'
@@ -377,7 +378,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
 
     def 'Update multiple data leaves error scenario: #scenario.'() {
         when: 'attempt to update data node for #scenario'
-            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, xpath, 'irrelevant json data', now)
+            objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, xpath, 'irrelevant json data', now, ContentType.JSON)
         then: 'a #expectedException is thrown'
             thrown(expectedException)
         where: 'the following data is used'
@@ -395,7 +396,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
             objectUnderTest.saveData(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now)
         when: 'the webinfo (container) is updated'
             json = '{"webinfo": {"domain-name":"newdomain.com" ,"contact-email":"info@newdomain.com" }}'
-            objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', json, now)
+            objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', json, now, ContentType.JSON)
         then: 'webinfo has been updated with teh new details'
             def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/webinfo', DIRECT_CHILDREN_ONLY)
             result.leaves.'domain-name'[0] == 'newdomain.com'
@@ -407,7 +408,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
     def 'Update bookstore top-level container data node.'() {
         when: 'the bookstore top-level container is updated'
             def json = '{ "bookstore": { "bookstore-name": "new bookstore" }}'
-            objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', json, now)
+            objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', json, now, ContentType.JSON)
         then: 'bookstore name has been updated'
             def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', DIRECT_CHILDREN_ONLY)
             result.leaves.'bookstore-name'[0] == 'new bookstore'
@@ -419,7 +420,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'Updated json for bookstore data'
             def jsonData =  "{'book-store:books':{'lang':'English/French','price':100,'title':'Matilda'}}"
         when: 'update is performed for leaves'
-            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now)
+            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now, ContentType.JSON)
         then: 'the updated data nodes are retrieved'
             def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
         and: 'the leaf values are updated as expected'
@@ -433,7 +434,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'Updated json for bookstore data'
             def jsonData =  "{'book-store:books':{'title':'Matilda', 'authors': ['beta', 'alpha', 'gamma', 'delta']}}"
         when: 'update is performed for leaves'
-            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now)
+            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now, ContentType.JSON)
         and: 'the updated data nodes are retrieved'
             def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
         then: 'the leaf-list values have expected order'
@@ -446,7 +447,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         given: 'Updated json for bookstore data'
             def jsonData =  "{'book-store:books':{'title':'Matilda', 'editions': [2011, 1988, 2001, 2022, 2025]}}"
         when: 'update is performed for leaves'
-            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now)
+            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now, ContentType.JSON)
         and: 'the updated data nodes are retrieved'
             def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
         then: 'the leaf-list values have natural order'
@@ -455,20 +456,22 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
             restoreBookstoreDataAnchor(2)
     }
 
-    def 'Get delta between 2 anchors for when #scenario'() {
+    def 'Get delta between 2 anchors'() {
         when: 'attempt to get delta report between anchors'
             def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, '/', OMIT_DESCENDANTS)
+        and: 'report is ordered based on xpath'
+            result = result.toList().sort { it.xpath }
         then: 'delta report contains expected number of changes'
             result.size() == 3
-        and: 'delta report contains UPDATE action with expected xpath'
-            assert result[0].getAction() == 'update'
+        and: 'delta report contains REPLACE action with expected xpath'
+            assert result[0].getAction() == 'replace'
             assert result[0].getXpath() == '/bookstore'
+        and: 'delta report contains CREATE action with expected xpath'
+            assert result[1].getAction() == 'create'
+            assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']"
         and: 'delta report contains REMOVE action with expected xpath'
-            assert result[1].getAction() == 'remove'
-            assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']"
-        and: 'delta report contains ADD action with expected xpath'
-            assert result[2].getAction() == 'add'
-            assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']"
+            assert result[2].getAction() == 'remove'
+            assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']"
     }
 
     def 'Get delta between 2 anchors returns empty response when #scenario'() {
@@ -512,11 +515,11 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
             'is empty'                   | "/bookstore/container-without-leaves"
     }
 
-    def 'Get delta between anchors for add action, where target data node #scenario'() {
+    def 'Get delta between anchors for "create" action, where target data node #scenario'() {
         when: 'attempt to get delta between leaves of data nodes present in 2 anchors'
             def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
         then: 'the expected action is present in delta report'
-            result.get(0).getAction() == 'add'
+            result.get(0).getAction() == 'create'
         and: 'the expected xapth is present in delta report'
             result.get(0).getXpath() == parentNodeXpath
         where: 'following data was used'
@@ -530,8 +533,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
     def 'Get delta between anchors when leaves of existing data nodes are updated,: #scenario'() {
         when: 'attempt to get delta between leaves of existing data nodes'
             def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, OMIT_DESCENDANTS)
-        then: 'expected action is update'
-            assert result[0].getAction() == 'update'
+        then: 'expected action is "replace"'
+            assert result[0].getAction() == 'replace'
         and: 'the payload has expected leaf values'
             def sourceData = result[0].getSourceData()
             def targetData = result[0].getTargetData()
@@ -547,8 +550,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
     def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() {
         when: 'attempt to get delta between leaves of existing data nodes'
             def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, DIRECT_CHILDREN_ONLY)
-        then: 'expected action is update'
-            assert result[0].getAction() == 'update'
+        then: 'expected action is "replace"'
+            assert result[0].getAction() == 'replace'
         and: 'the delta report has expected child node xpaths'
             def deltaReportEntities = getDeltaReportEntities(result)
             def childNodeXpathsInDeltaReport = deltaReportEntities.get('xpaths')
@@ -570,8 +573,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
         when: 'attempt to get delta between leaves of existing data nodes'
             def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
             def deltaReportEntities = getDeltaReportEntities(result)
-        then: 'expected action is update'
-            assert result[0].getAction() == 'update'
+        then: 'expected action is "replace"'
+            assert result[0].getAction() == 'replace'
         and: 'the payload has expected parent node xpath'
             assert deltaReportEntities.get('xpaths').contains(parentNodeXpath)
         and: 'delta report has expected source and target data'
@@ -584,6 +587,46 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
             assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode)
     }
 
+    def 'Get delta between anchor and JSON payload'() {
+        when: 'attempt to get delta report between anchor and JSON payload'
+            def jsonPayload = "{\"book-store:bookstore\":{\"bookstore-name\":\"Crossword Bookstores\"},\"book-store:bookstore-address\":{\"address\":\"Bangalore, India\",\"postal-code\":\"560062\",\"bookstore-name\":\"Crossword Bookstores\"}}"
+            def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, OMIT_DESCENDANTS)
+        then: 'delta report contains expected number of changes'
+            result.size() == 3
+        and: 'delta report contains "replace" action with expected xpath'
+            assert result[0].getAction() == 'replace'
+            assert result[0].getXpath() == '/bookstore'
+        and: 'delta report contains "remove" action with expected xpath'
+            assert result[1].getAction() == 'remove'
+            assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']"
+        and: 'delta report contains "create" action with expected xpath'
+            assert result[2].getAction() == 'create'
+            assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']"
+    }
+
+    def 'Get delta between anchor and payload returns empty response when JSON payload is identical to anchor data'() {
+        when: 'attempt to get delta report between anchor and JSON payload (replacing the string Easons with Easons-1 because the data in JSON file is modified, to append anchor number, during the setup process of the integration tests)'
+            def jsonPayload = readResourceDataFile('bookstore/bookstoreData.json').replace('Easons', 'Easons-1')
+            def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, INCLUDE_ALL_DESCENDANTS)
+        then: 'delta report is empty'
+            assert result.isEmpty()
+    }
+
+    def 'Get delta between anchor and payload error scenario: #scenario'() {
+        when: 'attempt to get delta between anchor and json payload'
+            objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, sourceAnchor, xpath, [:], jsonPayload, INCLUDE_ALL_DESCENDANTS)
+        then: 'expected exception is thrown'
+            thrown(expectedException)
+        where: 'following data was used'
+                scenario                               | dataspaceName               | sourceAnchor          | xpath        | jsonPayload   || expectedException
+        'invalid dataspace name'                       | 'Invalid dataspace'         | 'not-relevant'        | '/'          | '{some-json}' || DataValidationException
+        'invalid anchor name'                          | FUNCTIONAL_TEST_DATASPACE_3 | 'invalid anchor'      | '/'          | '{some-json}' || DataValidationException
+        'non-existing dataspace'                       | 'non-existing'              | 'not-relevant'        | '/'          | '{some-json}' || DataspaceNotFoundException
+        'non-existing anchor'                          | FUNCTIONAL_TEST_DATASPACE_3 | 'non-existing-anchor' | '/'          | '{some-json}' || AnchorNotFoundException
+        'empty json payload with root node xpath'      | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3    | '/'          | ''            || DataValidationException
+        'empty json payload with non-root node xpath'  | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3    | '/bookstore' | ''            || DataValidationException
+    }
+
     def getDeltaReportEntities(List<DeltaReport> deltaReport) {
         def xpaths = []
         def action = []
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.cps
 
 import org.onap.cps.api.CpsDataspaceService
-import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.integration.base.FunctionalSpecBase
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.DataspaceInUseException
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 
-class CpsDataspaceServiceIntegrationSpec extends CpsIntegrationSpecBase {
+class DataspaceServiceIntegrationSpec extends FunctionalSpecBase {
 
     CpsDataspaceService objectUnderTest
 
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.cps
 
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.integration.base.FunctionalSpecBase
@@ -31,7 +31,7 @@ import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
 
-class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
+class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
 
     CpsModuleService objectUnderTest
 
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
@@ -19,7 +19,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.cps
 
 import java.time.OffsetDateTime
 import org.onap.cps.api.CpsQueryService
@@ -33,7 +33,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
 import static org.onap.cps.spi.PaginationOption.NO_PAGINATION
 
-class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
+class QueryServiceIntegrationSpec extends FunctionalSpecBase {
 
     CpsQueryService objectUnderTest
 
@@ -382,7 +382,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             def result = objectUnderTest.queryDataNodesAcrossAnchors(FUNCTIONAL_TEST_DATASPACE_1, '/bookstore', OMIT_DESCENDANTS, new PaginationOption(pageIndex, pageSize))
         then: 'correct bookstore names are queried'
             def bookstoreNames = result.collect { it.getLeaves().get('bookstore-name') }
-            assert bookstoreNames.toList() == expectedBookstoreNames
+            assert bookstoreNames.toSet() == expectedBookstoreNames.toSet()
         and: 'the correct number of page size is returned'
             assert result.size() == expectedPageSize
         and: 'the queried nodes have expected anchor names'
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.cps
 
 import org.onap.cps.integration.base.FunctionalSpecBase
+import org.onap.cps.ri.utils.SessionManager
 import org.onap.cps.spi.exceptions.SessionManagerException
-import org.onap.cps.spi.utils.SessionManager
 
 class SessionManagerIntegrationSpec extends FunctionalSpecBase {
 
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy
new file mode 100644 (file)
index 0000000..222b3c0
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+
+class AlternateIdSpec extends CpsIntegrationSpecBase {
+
+    def setup() {
+        dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
+        registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'alternateId')
+    }
+
+    def cleanup() {
+        deregisterCmHandle(DMI1_URL, 'ch-1')
+    }
+
+    def 'AlternateId in pass-through data operations should return OK status.'() {
+        given: 'the URL for the pass-through data request'
+            def url = '/ncmp/v1/ch/alternateId/data/ds/ncmp-datastore:passthrough-running'
+        when: 'a pass-through data request is sent to NCMP'
+            def response = mvc.perform(get(url)
+                    .queryParam('resourceIdentifier', 'my-resource-id')
+                    .contentType(MediaType.APPLICATION_JSON))
+                    .andReturn().response
+        then: 'response status is Ok'
+            assert response.status == HttpStatus.OK.value()
+    }
+
+
+
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+import spock.util.concurrent.PollingConditions
 
-import static org.springframework.http.HttpMethod.GET
 import static org.springframework.http.HttpMethod.DELETE
+import static org.springframework.http.HttpMethod.GET
 import static org.springframework.http.HttpMethod.PATCH
 import static org.springframework.http.HttpMethod.POST
 import static org.springframework.http.HttpMethod.PUT
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
 
-import okhttp3.mockwebserver.Dispatcher
-import okhttp3.mockwebserver.MockResponse
-import okhttp3.mockwebserver.RecordedRequest
-import org.jetbrains.annotations.NotNull
-import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.springframework.http.HttpHeaders
-import org.springframework.http.HttpStatus
-import org.springframework.http.MediaType
-import spock.util.concurrent.PollingConditions
-
-class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase {
-
-    def lastAuthHeaderReceived = null
+class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase {
 
     def setup() {
-        dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
-        registerCmHandle(DMI_URL, 'ch-1', NO_MODULE_SET_TAG)
-
-        mockDmiServer.setDispatcher(new Dispatcher() {
-            @Override
-            MockResponse dispatch(@NotNull RecordedRequest request) throws InterruptedException {
-                if (request.path == '/actuator/health') {
-                        return new MockResponse()
-                                .addHeader("Content-Type", MediaType.APPLICATION_JSON).setBody('{"status":"UP"}')
-                                .setResponseCode(HttpStatus.OK.value())
-                } else {
-                    lastAuthHeaderReceived = request.getHeader('Authorization')
-                    return new MockResponse().setResponseCode(HttpStatus.OK.value())
-                }
-            }
-        })
+        dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
+        registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG)
     }
 
     def cleanup() {
-        deregisterCmHandle(DMI_URL, 'ch-1')
+        deregisterCmHandle(DMI1_URL, 'ch-1')
     }
 
     def 'Bearer token is passed from NCMP to DMI in pass-through data operations.'() {
@@ -75,7 +54,7 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase {
                     .andExpect(status().is2xxSuccessful())
 
         then: 'DMI has received request with bearer token'
-            lastAuthHeaderReceived == 'Bearer some-bearer-token'
+            assert dmiDispatcher1.lastAuthHeaderReceived == 'Bearer some-bearer-token'
 
         where: 'all HTTP operations are applied'
             httpMethod << [GET, POST, PUT, PATCH, DELETE]
@@ -91,7 +70,7 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase {
                     .andExpect(status().is2xxSuccessful())
 
         then: 'DMI has received request with no authorization header'
-            lastAuthHeaderReceived == null
+            assert dmiDispatcher1.lastAuthHeaderReceived == null
 
         where: 'all HTTP operations are applied'
             httpMethod << [GET, POST, PUT, PATCH, DELETE]
@@ -115,7 +94,7 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase {
 
         then: 'DMI will receive the async request with bearer token'
             new PollingConditions().within(3, () -> {
-                assert lastAuthHeaderReceived == 'Bearer some-bearer-token'
+                assert dmiDispatcher1.lastAuthHeaderReceived == 'Bearer some-bearer-token'
             })
     }
 
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.ncmp
 
-import java.time.Duration
-import java.time.OffsetDateTime
 import org.apache.kafka.common.TopicPartition
 import org.apache.kafka.common.serialization.StringDeserializer
 import org.onap.cps.integration.KafkaTestContainer
 import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.NcmpResponseStatus
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory
 import spock.util.concurrent.PollingConditions
 
-class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
+import java.time.Duration
+import java.time.OffsetDateTime
+
+class CmHandleCreateSpec extends CpsIntegrationSpecBase {
 
-    NetworkCmProxyDataService objectUnderTest
+    NetworkCmProxyInventoryFacade objectUnderTest
 
     def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class)
 
     def setup() {
-        objectUnderTest = networkCmProxyDataService
+        objectUnderTest = networkCmProxyInventoryFacade
     }
 
     def 'CM Handle registration is successful.'() {
         given: 'DMI will return modules when requested'
-            dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
+            dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
 
         and: 'consumer subscribed to topic'
             kafkaConsumer.subscribe(['ncmp-events'])
 
         when: 'a CM-handle is registered for creation'
             def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])
-            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])
+            def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
 
         then: 'registration gives successful response'
             assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')]
@@ -63,11 +65,8 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
         and: 'CM-handle is initially in ADVISED state'
             assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState
 
-        when: 'module sync runs'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
-
-        then: 'CM-handle goes to READY state'
-            new PollingConditions().within(3, () -> {
+        and: 'CM-handle goes to READY state after module sync'
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState
             })
 
@@ -83,23 +82,20 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
             assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
 
         cleanup: 'deregister CM handle'
-            deregisterCmHandle(DMI_URL, 'ch-1')
+            deregisterCmHandle(DMI1_URL, 'ch-1')
     }
 
     def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() {
         given: 'DMI is not available to handle requests'
-            dmiDispatcher.isAvailable = false
+            dmiDispatcher1.isAvailable = false
 
         when: 'a CM-handle is registered for creation'
             def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])
-            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-
-        and: 'module sync runs'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
 
         then: 'CM-handle goes to LOCKED state with reason MODULE_SYNC_FAILED'
-            new PollingConditions().within(3, () -> {
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState('ch-1')
                 assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED
                 assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_SYNC_FAILED
@@ -109,23 +105,22 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
             assert objectUnderTest.getYangResourcesModuleReferences('ch-1').empty
 
         cleanup: 'deregister CM handle'
-            deregisterCmHandle(DMI_URL, 'ch-1')
+            deregisterCmHandle(DMI1_URL, 'ch-1')
     }
 
     def 'Create a CM-handle with existing moduleSetTag.'() {
         given: 'DMI will return modules when requested'
-            dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']]
+            dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']]
         and: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"'
-            registerCmHandle(DMI_URL, 'ch-1', 'A')
-            registerCmHandle(DMI_URL, 'ch-2', 'B')
+            registerCmHandle(DMI1_URL, 'ch-1', 'A')
+            registerCmHandle(DMI1_URL, 'ch-2', 'B')
 
         when: 'a CM-handle is registered for creation with moduleSetTag "B"'
             def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B')
-            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate]))
+            objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate]))
 
-        then: 'the CM-handle goes to READY state after module sync'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
-            new PollingConditions().within(3, () -> {
+        then: 'the CM-handle goes to READY state'
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-3').cmHandleState
             })
 
@@ -136,53 +131,71 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
             assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-3').moduleName.sort()
 
         cleanup: 'deregister CM handles'
-            deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2', 'ch-3'])
+            deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2', 'ch-3'])
+    }
+
+    def 'Create CM-handles with alternate IDs.'() {
+        given: 'DMI will return modules for all CM-handles when requested'
+            dmiDispatcher1.moduleNamesPerCmHandleId = (1..7).collectEntries{ ['ch-'+it, ['M1']] }
+        and: 'an existing CM-handle with an alternate ID'
+            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'existing-alt-id')
+        and: 'an existing CM-handle with no alternate ID'
+            registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, NO_ALTERNATE_ID)
+
+        when: 'a batch of CM-handles is registered for creation with various alternate IDs'
+            def cmHandlesToCreate = [
+                    new NcmpServiceCmHandle(cmHandleId: 'ch-3', alternateId: NO_ALTERNATE_ID),
+                    new NcmpServiceCmHandle(cmHandleId: 'ch-4', alternateId: 'unique-alt-id'),
+                    new NcmpServiceCmHandle(cmHandleId: 'ch-5', alternateId: 'existing-alt-id'),
+                    new NcmpServiceCmHandle(cmHandleId: 'ch-6', alternateId: 'duplicate-alt-id'),
+                    new NcmpServiceCmHandle(cmHandleId: 'ch-7', alternateId: 'duplicate-alt-id'),
+            ]
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate)
+            def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+
+        then: 'registration gives expected responses'
+            assert dmiPluginRegistrationResponse.createdCmHandles.sort { it.cmHandle } == [
+                CmHandleRegistrationResponse.createSuccessResponse('ch-3'),
+                CmHandleRegistrationResponse.createSuccessResponse('ch-4'),
+                CmHandleRegistrationResponse.createFailureResponse('ch-5', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED),
+                CmHandleRegistrationResponse.createSuccessResponse('ch-6'),
+                CmHandleRegistrationResponse.createFailureResponse('ch-7', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED),
+            ]
+
+        cleanup: 'deregister CM handles'
+            deregisterCmHandles(DMI1_URL, (1..7).collect{ 'ch-'+it })
     }
 
     def 'CM Handle retry after failed module sync.'() {
         given: 'DMI is not initially available to handle requests'
-            dmiDispatcher.isAvailable = false
+            dmiDispatcher1.isAvailable = false
 
         when: 'CM-handles are registered for creation'
             def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')]
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: cmHandlesToCreate)
-            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        and: 'module sync runs'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate)
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'CM-handles go to LOCKED state'
-            new PollingConditions().within(3, () -> {
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.LOCKED
-                assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.LOCKED
             })
 
-        when: 'we wait for LOCKED CM handle retry time (actually just subtract 3 minutes from handles lastUpdateTime)'
-            overrideCmHandleLastUpdateTime('ch-1', OffsetDateTime.now().minusMinutes(3))
-            overrideCmHandleLastUpdateTime('ch-2', OffsetDateTime.now().minusMinutes(3))
-        and: 'failed CM handles are reset'
-            moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
-        then: 'CM-handles are ADVISED state'
-            assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.ADVISED
-            assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.ADVISED
-
         when: 'DMI is available for retry'
-            dmiDispatcher.isAvailable = true
-        and: 'DMI will return expected modules'
-            dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']]
-        and: 'module sync runs'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
-        then: 'CM-handles go to READY state'
-            new PollingConditions().within(3, () -> {
-                assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.READY
-                assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.READY
+            dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M2']]
+            dmiDispatcher1.isAvailable = true
+
+        then: 'Both CM-handles go to READY state'
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
+                ['ch-1', 'ch-2'].each { cmHandleId ->
+                    assert objectUnderTest.getCmHandleCompositeState(cmHandleId).cmHandleState == CmHandleState.READY
+                }
             })
-        and: 'CM-handles have expected modules'
-            assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
-            assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-2').moduleName.sort()
-        and: 'CM-handles have expected module set tags (blank)'
-            assert objectUnderTest.getNcmpServiceCmHandle('ch-1').moduleSetTag == ''
-            assert objectUnderTest.getNcmpServiceCmHandle('ch-2').moduleSetTag == ''
 
-        cleanup: 'deregister CM handle'
-            deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2'])
+        and: 'Both CM-handles have expected modules'
+            ['ch-1', 'ch-2'].each { cmHandleId ->
+                assert objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() == ['M1', 'M2']
+            }
+
+        cleanup: 'deregister CM handles'
+            deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2'])
     }
 }
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy
new file mode 100644 (file)
index 0000000..2d1588e
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.api.NcmpResponseStatus
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+
+class CmHandleUpdateSpec extends CpsIntegrationSpecBase {
+
+    NetworkCmProxyInventoryFacade objectUnderTest
+
+    def setup() {
+        objectUnderTest = networkCmProxyInventoryFacade
+    }
+
+    def 'Update of CM-handle with new or unchanged alternate ID succeeds.'() {
+        given: 'DMI will return modules when requested'
+            dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2']]
+        and: "existing CM-handle with alternate ID: $oldAlternateId"
+            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, oldAlternateId)
+
+        when: "CM-handle is registered for update with new alternate ID: $newAlternateId"
+            def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: newAlternateId)
+            def dmiPluginRegistrationResponse =
+                    objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate]))
+
+        then: 'registration gives successful response'
+            assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')]
+
+        and: 'the CM-handle has expected alternate ID'
+            assert objectUnderTest.getNcmpServiceCmHandle('ch-1').alternateId == expectedAlternateId
+
+        cleanup: 'deregister CM handles'
+            deregisterCmHandle(DMI1_URL, 'ch-1')
+
+        where:
+            oldAlternateId | newAlternateId || expectedAlternateId
+            ''             | ''             || ''
+            ''             | 'new'          || 'new'
+            'old'          | 'old'          || 'old'
+            'old'          | null           || 'old'
+            'old'          | ''             || 'old'
+            'old'          | '  '           || 'old'
+    }
+
+    def 'Update of CM-handle with previously set alternate ID fails.'() {
+        given: 'DMI will return modules when requested'
+            dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2']]
+        and: 'existing CM-handle with alternate ID'
+            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'original')
+
+        when: 'a CM-handle is registered for update with new alternate ID'
+            def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'new')
+            def dmiPluginRegistrationResponse =
+                    objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate]))
+
+        then: 'registration gives failure response, due to alternate ID being already associated'
+            assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createFailureResponse('ch-1', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED)]
+
+        and: 'the CM-handle still has the old alternate ID'
+            assert objectUnderTest.getNcmpServiceCmHandle('ch-1').alternateId == 'original'
+
+        cleanup: 'deregister CM handles'
+            deregisterCmHandle(DMI1_URL, 'ch-1')
+    }
+
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.ncmp
 
 import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
-import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.UpgradedCmHandles
+import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
+import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles
+import org.onap.cps.ncmp.impl.inventory.models.CmHandleState
+import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory
 import spock.util.concurrent.PollingConditions
 
-class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
+class CmHandleUpgradeSpec extends CpsIntegrationSpecBase {
 
-    NetworkCmProxyDataService objectUnderTest
+    NetworkCmProxyInventoryFacade objectUnderTest
 
     static final CM_HANDLE_ID = 'ch-1'
     static final CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG = 'ch-2'
 
     def setup() {
-        objectUnderTest = networkCmProxyDataService
+        objectUnderTest = networkCmProxyInventoryFacade
     }
 
     def 'Upgrade CM-handle with new moduleSetTag or no moduleSetTag.'() {
         given: 'a CM-handle is created with expected initial modules: M1 and M2'
-            dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
-            registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag)
+            dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
+            registerCmHandle(DMI1_URL, CM_HANDLE_ID, initialModuleSetTag)
             assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
 
         when: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'"
             def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag)
-            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
-                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
+            def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade))
 
         then: 'registration gives successful response'
             assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)]
@@ -61,15 +61,12 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
             assert cmHandleCompositeState.lockReason.details == "Upgrade to ModuleSetTag: ${updatedModuleSetTag}"
 
         when: 'DMI will return different modules for upgrade: M1 and M3'
-            dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3']
-        and: 'module sync runs'
-            moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+            dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3']
 
         then: 'CM-handle goes to READY state'
-            new PollingConditions().eventually {
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState
-            }
+            })
 
         and: 'the CM-handle has expected moduleSetTag'
             assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag
@@ -78,7 +75,7 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
             assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
 
         cleanup: 'deregister CM-handle'
-            deregisterCmHandle(DMI_URL, CM_HANDLE_ID)
+            deregisterCmHandle(DMI1_URL, CM_HANDLE_ID)
 
         where:
             initialModuleSetTag | updatedModuleSetTag
@@ -90,31 +87,27 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
 
     def 'Upgrade CM-handle with existing moduleSetTag.'() {
         given: 'DMI will return modules for registration'
-            dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
-            dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG] = ['M1', 'M3']
+            dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
+            dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG] = ['M1', 'M3']
         and: "an existing CM-handle handle with moduleSetTag '${updatedModuleSetTag}'"
-            registerCmHandle(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, updatedModuleSetTag)
+            registerCmHandle(DMI1_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, updatedModuleSetTag)
             assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG).moduleName.sort()
         and: "a CM-handle with moduleSetTag '${initialModuleSetTag}' which will be upgraded"
-            registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag)
+            registerCmHandle(DMI1_URL, CM_HANDLE_ID, initialModuleSetTag)
             assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
 
         when: "CM-handle is upgraded to moduleSetTag '${updatedModuleSetTag}'"
             def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag)
-            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
-                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
+            def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade))
 
         then: 'registration gives successful response'
             assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)]
 
-        when: 'module sync runs'
-            moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
-
-        then: 'CM-handle goes to READY state'
-            new PollingConditions().eventually {
+        and: 'CM-handle goes to READY state'
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState
-            }
+            })
 
         and: 'the CM-handle has expected moduleSetTag'
             assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag
@@ -123,7 +116,7 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
             assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
 
         cleanup: 'deregister CM-handle'
-            deregisterCmHandles(DMI_URL, [CM_HANDLE_ID, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG])
+            deregisterCmHandles(DMI1_URL, [CM_HANDLE_ID, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG])
 
         where:
             initialModuleSetTag | updatedModuleSetTag
@@ -133,14 +126,14 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
 
     def 'Skip upgrade of CM-handle with same moduleSetTag as before.'() {
         given: 'an existing CM-handle with expected initial modules: M1 and M2'
-            dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
-            registerCmHandle(DMI_URL, CM_HANDLE_ID, 'same')
+            dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
+            registerCmHandle(DMI1_URL, CM_HANDLE_ID, 'same')
             assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
 
         when: 'CM-handle is upgraded with the same moduleSetTag'
             def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'same')
-            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
-                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
+            objectUnderTest.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade))
 
         then: 'CM-handle remains in READY state'
             assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState
@@ -152,37 +145,33 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
             assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
 
         cleanup: 'deregister CM-handle'
-            deregisterCmHandle(DMI_URL, CM_HANDLE_ID)
+            deregisterCmHandle(DMI1_URL, CM_HANDLE_ID)
     }
 
     def 'Upgrade of CM-handle fails due to DMI error.'() {
         given: 'a CM-handle exists'
-            dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
-            registerCmHandle(DMI_URL, CM_HANDLE_ID, 'oldTag')
+            dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2']
+            registerCmHandle(DMI1_URL, CM_HANDLE_ID, 'oldTag')
         and: 'DMI is not available for upgrade'
-            dmiDispatcher.isAvailable = false
+            dmiDispatcher1.isAvailable = false
 
         when: 'the CM-handle is upgraded'
             def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'newTag')
-            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
-                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
-
-        and: 'module sync runs'
-            moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+            objectUnderTest.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade))
 
         then: 'CM-handle goes to LOCKED state with reason MODULE_UPGRADE_FAILED'
-            new PollingConditions(timeout: 3).eventually {
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID)
                 assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED
                 assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE_FAILED
-            }
+            })
 
         and: 'the CM-handle has same moduleSetTag as before'
             assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'oldTag'
 
         cleanup: 'deregister CM-handle'
-            deregisterCmHandle(DMI_URL, CM_HANDLE_ID)
+            deregisterCmHandle(DMI1_URL, CM_HANDLE_ID)
     }
 
 }
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.ncmp
 
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
-import org.onap.cps.integration.base.CpsIntegrationSpecBase;
-import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService
+import org.springframework.beans.factory.annotation.Autowired
 
-class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase {
+import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
+
+class CmNotificationSubscriptionSpec extends CpsIntegrationSpecBase {
 
     @Autowired
-    CmNotificationSubscriptionPersistenceService cmNotificationSubscriptionPersistenceService;
+    CmSubscriptionPersistenceService cmSubscriptionPersistenceService
 
     def 'Adding a new cm notification subscription'() {
         given: 'there is no ongoing cm subscription for the following'
             def datastoreType = PASSTHROUGH_RUNNING
             def cmHandleId = 'ch-1'
             def xpath = '/x/y'
-            assert cmNotificationSubscriptionPersistenceService.
-                getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 0
+            assert cmSubscriptionPersistenceService.
+                getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() == 0
         when: 'we add a new cm notification subscription'
-            cmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(datastoreType,cmHandleId,xpath,
+            cmSubscriptionPersistenceService.addCmSubscription(datastoreType, cmHandleId, xpath,
                 'subId-1')
         then: 'there is an ongoing cm subscription for that CM handle and xpath'
-            assert cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,xpath)
+            assert cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, xpath)
         and: 'only one subscription id is related to now ongoing cm subscription'
-            assert cmNotificationSubscriptionPersistenceService.
-                getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 1
+            assert cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() == 1
     }
 
     def 'Adding a cm notification subscription to the already existing cm handle but non existing xpath'() {
@@ -52,18 +52,17 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase {
             def datastoreType = PASSTHROUGH_RUNNING
             def cmHandleId = 'ch-1'
             def existingXpath = '/x/y'
-            assert cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,existingXpath)
+            assert cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, existingXpath)
         and: 'a non existing cm subscription with same datastore name and cm handle but different xpath'
             def nonExistingXpath = '/x2/y2'
-            assert !cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,nonExistingXpath)
+            assert !cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, nonExistingXpath)
         when: 'a new cm notification subscription is made for the existing cm handle and non existing xpath'
-            cmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(datastoreType,cmHandleId, nonExistingXpath,
+            cmSubscriptionPersistenceService.addCmSubscription(datastoreType, cmHandleId, nonExistingXpath,
                 'subId-2')
         then: 'there is an ongoing cm subscription for that CM handle and xpath'
-            assert cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,nonExistingXpath)
+            assert cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, nonExistingXpath)
         and: 'only one subscription id is related to now ongoing cm subscription'
-            assert cmNotificationSubscriptionPersistenceService.
-                getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,nonExistingXpath).size() == 1
+            assert cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, nonExistingXpath).size() == 1
     }
 
     def 'Adding a cm notification subscription to the already existing cm handle and xpath'() {
@@ -72,10 +71,10 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase {
             def cmHandleId = 'ch-1'
             def xpath = '/x/y'
         when: 'a new cm notification subscription is made for the SAME CM handle and xpath'
-            cmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(datastoreType,cmHandleId,xpath,
+            cmSubscriptionPersistenceService.addCmSubscription(datastoreType, cmHandleId, xpath,
                 'subId-3')
         then: 'it is added to the ongoing list of subscription ids'
-            def subscriptionIds = cmNotificationSubscriptionPersistenceService.getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath)
+            def subscriptionIds = cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath)
             assert subscriptionIds.size() == 2
         and: 'both subscription ids exists for the CM handle and xpath'
             assert subscriptionIds.contains("subId-1") && subscriptionIds.contains("subId-3")
@@ -88,13 +87,12 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase {
             def xpath = '/x/y'
         and: 'the number of subscribers is as follows'
             def originalNumberOfSubscribers =
-                cmNotificationSubscriptionPersistenceService.getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size()
+                cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size()
         when: 'a subscriber is removed'
-            cmNotificationSubscriptionPersistenceService.removeCmNotificationSubscription(datastoreType,cmHandleId,xpath,'subId-3')
+            cmSubscriptionPersistenceService.removeCmSubscription(datastoreType, cmHandleId, xpath, 'subId-3')
         then: 'the number of subscribers is reduced by 1'
-            def updatedNumberOfSubscribers =
-                cmNotificationSubscriptionPersistenceService.getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size()
-            assert updatedNumberOfSubscribers == originalNumberOfSubscribers-1
+            def updatedNumberOfSubscribers = cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size()
+            assert updatedNumberOfSubscribers == originalNumberOfSubscribers - 1
     }
 
     def 'Removing the LAST cm notification subscriber for a given cm handle, datastore and xpath'() {
@@ -103,12 +101,12 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase {
             def cmHandleId = 'ch-1'
             def xpath = '/x/y'
         and: 'there is only one subscriber'
-            assert cmNotificationSubscriptionPersistenceService
-                .getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 1
+            assert cmSubscriptionPersistenceService
+                .getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() == 1
         when: 'only subscriber is removed'
-            cmNotificationSubscriptionPersistenceService.removeCmNotificationSubscription(datastoreType,cmHandleId,xpath,'subId-1')
+            cmSubscriptionPersistenceService.removeCmSubscription(datastoreType, cmHandleId, xpath, 'subId-1')
         then: 'there are no longer any subscriptions for the cm handle, datastore and xpath'
-            assert !cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType, cmHandleId, xpath)
+            assert !cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, xpath)
     }
 
 }
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy
new file mode 100644 (file)
index 0000000..4d04eee
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.api.datajobs.DataJobResultService
+import org.springframework.beans.factory.annotation.Autowired
+
+class DataJobResultServiceSpec extends CpsIntegrationSpecBase {
+
+    @Autowired
+    DataJobResultService dataJobResultService;
+
+    def 'Get the status of a data job from DMI.'() {
+        given: 'the required data about the data job'
+            def authorization = 'my authorization header'
+            def dmiServiceName = DMI1_URL
+            def dataProducerId = 'some-data-producer-id'
+            def dataProducerJobId = 'some-data-producer-job-id'
+            def destination = 'some-destination'
+        when: 'the data job status checked'
+            def result = dataJobResultService.getDataJobResult(authorization, dmiServiceName, dataProducerId, dataProducerJobId, destination)
+        then: 'the status is that defined in the mock service.'
+            assert result == '{ "result": "some result"}'
+    }
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy
new file mode 100644 (file)
index 0000000..6e5c0e4
--- /dev/null
@@ -0,0 +1,23 @@
+package org.onap.cps.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.api.datajobs.DataJobStatusService
+import org.springframework.beans.factory.annotation.Autowired
+
+class DataJobStatusServiceSpec extends CpsIntegrationSpecBase {
+
+    @Autowired
+    DataJobStatusService dataJobStatusService
+
+    def 'Get the status of a data job from DMI.'() {
+        given: 'the required data about the data job'
+            def dmiServiceName = DMI1_URL
+            def dataProducerId = 'some-data-producer-id'
+            def dataProducerJobId = 'some-data-producer-job-id'
+            def authorization = 'my authorization header'
+        when: 'the data job status checked'
+            def result = dataJobStatusService.getDataJobStatus(authorization, dmiServiceName, dataProducerId, dataProducerJobId)
+        then: 'the status is that defined in the mock service.'
+            assert result == 'status details from mock service'
+    }
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DmiUrlEncodingPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DmiUrlEncodingPassthroughSpec.groovy
new file mode 100644 (file)
index 0000000..4e9b809
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.MediaType
+
+import static org.springframework.http.HttpMethod.DELETE
+import static org.springframework.http.HttpMethod.GET
+import static org.springframework.http.HttpMethod.PATCH
+import static org.springframework.http.HttpMethod.POST
+import static org.springframework.http.HttpMethod.PUT
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+class DmiUrlEncodingPassthroughSpec extends CpsIntegrationSpecBase {
+
+    def setup() {
+        dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
+        registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG)
+    }
+
+    def cleanup() {
+        deregisterCmHandle(DMI1_URL, 'ch-1')
+    }
+
+    def 'DMI URL encoding for pass-through operational data operations with GET request'() {
+        when: 'sending a GET pass-through data request to NCMP'
+            mvc.perform(request(GET, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-operational')
+                    .queryParam('resourceIdentifier', 'parent/child')
+                    .queryParam('options', '(a=1,b=2)'))
+                    .andExpect(status().is2xxSuccessful())
+        then: 'verify that DMI received the request with the correctly encoded URL'
+            assert dmiDispatcher1.dmiResourceDataUrl == '/dmi/v1/ch/ch-1/data/ds/ncmp-datastore%3Apassthrough-operational?resourceIdentifier=parent%2Fchild&options=%28a%3D1%2Cb%3D2%29'
+    }
+
+    def 'DMI URL encoding for pass-through running data operations with POST request'() {
+        when: 'sending a pass-through data request to NCMP with various HTTP methods'
+            mvc.perform(request(POST, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running')
+                    .queryParam('resourceIdentifier', 'parent/child')
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content('{ "some-json": "data" }'))
+                    .andExpect(status().is2xxSuccessful())
+        then: 'verify that DMI received the request with the correctly encoded URL'
+            assert dmiDispatcher1.dmiResourceDataUrl == '/dmi/v1/ch/ch-1/data/ds/ncmp-datastore%3Apassthrough-running?resourceIdentifier=parent%2Fchild'
+    }
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy
new file mode 100644 (file)
index 0000000..99f245a
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+
+import static org.springframework.http.HttpMethod.POST
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request
+
+class PolicyExecutorIntegrationSpec extends CpsIntegrationSpecBase {
+
+    def setup() {
+        // Enable mocked policy executor logic
+        policyDispatcher.allowAll = false;
+        //minimum setup for 2 cm handles with alternate ids
+        dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': [], 'ch-2': []]
+        registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'fdn1')
+        registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'fdn2')
+    }
+
+    def cleanup() {
+        deregisterCmHandle(DMI1_URL, 'ch-1')
+        deregisterCmHandle(DMI1_URL, 'ch-2')
+    }
+
+    def 'Policy Executor create request with #scenario.'() {
+        when: 'a pass-through write request is sent to NCMP'
+            def response = mvc.perform(request(POST, "/ncmp/v1/ch/$cmHandle/data/ds/ncmp-datastore:passthrough-running")
+                    .queryParam('resourceIdentifier', 'my-resource-id')
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content('{ "some-json": "data" }')
+                    .header(HttpHeaders.AUTHORIZATION, authorization))
+                    .andReturn().response
+        then: 'the expected status code is returned'
+            response.getStatus() == execpectedStatusCode
+        where: 'following parameters are used'
+            scenario                | cmHandle | authorization         || execpectedStatusCode
+            'accepted cm handle'    | 'ch-1'   | 'mock expects "ABC"'  || 201
+            'un-accepted cm handle' | 'ch-2'   | 'mock expects "ABC"'  || 409
+            'invalid authorization' | 'ch-1'   | 'something else'      || 500
+    }
+
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration.functional
+package org.onap.cps.integration.functional.ncmp
 
-import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.springframework.http.MediaType
-import spock.util.concurrent.PollingConditions
 import static org.hamcrest.Matchers.containsInAnyOrder
 import static org.hamcrest.Matchers.hasSize
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@@ -30,29 +27,31 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
 
-class NcmpRestApiSpec extends CpsIntegrationSpecBase {
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.MediaType
+import spock.util.concurrent.PollingConditions
+
+class RestApiSpec extends CpsIntegrationSpecBase {
 
     def 'Register CM Handles using REST API.'() {
         given: 'DMI will return modules'
-            dmiDispatcher.moduleNamesPerCmHandleId = [
+            dmiDispatcher1.moduleNamesPerCmHandleId = [
                 'ch-1': ['M1', 'M2'],
                 'ch-2': ['M1', 'M2'],
                 'ch-3': ['M1', 'M3']
             ]
-        and: 'a POST request is made to register the CM Handles'
-            def requestBody = '{"dmiPlugin":"'+DMI_URL+'","createdCmHandles":[{"cmHandle":"ch-1"},{"cmHandle":"ch-2"},{"cmHandle":"ch-3"}]}'
+        when: 'a POST request is made to register the CM Handles'
+            def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1","alternateId":"alt-1"},{"cmHandle":"ch-2","alternateId":"alt-2"},{"cmHandle":"ch-3","alternateId":"alt-3"}]}'
             mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody))
                     .andExpect(status().is2xxSuccessful())
-        when: 'module sync runs'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
         then: 'CM-handles go to READY state'
-            new PollingConditions().eventually {
+            new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> {
                 (1..3).each {
                     mvc.perform(get('/ncmp/v1/ch/ch-'+it))
                             .andExpect(status().isOk())
                             .andExpect(jsonPath('$.state.cmHandleState').value('READY'))
                 }
-            }
+            })
     }
 
     def 'Search for CM Handles by module using REST API.'() {
@@ -77,9 +76,30 @@ class NcmpRestApiSpec extends CpsIntegrationSpecBase {
             'M3'       || ['ch-3']
     }
 
+    def 'Search for CM Handles using Cps Path Query.'() {
+        given: 'a JSON request body containing search parameter'
+            def requestBodyWithSearchCondition = """{
+                    "cmHandleQueryParameters": [
+                            {
+                                "conditionName": "cmHandleWithCpsPath",
+                                "conditionParameters": [ {"cpsPath" : "%s"} ]
+                            }
+                    ]
+                }""".formatted(cpsPath)
+        expect: "a search for cps path ${cpsPath} returns expected CM handles"
+            mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithSearchCondition))
+                    .andExpect(status().is2xxSuccessful())
+                    .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray())))
+                    .andExpect(jsonPath('$', hasSize(expectedCmHandles.size())));
+        where:
+            scenario                    | cpsPath                                 || expectedCmHandles
+            'All Ready CM handles'      | "//state[@cm-handle-state='READY']"     || ['ch-1', 'ch-2', 'ch-3']
+            'Having Alternate ID alt-3' | "//cm-handles[@alternate-id='alt-3']"   || ['ch-3']
+    }
+
     def 'De-register CM handles using REST API.'() {
         when: 'a POST request is made to deregister the CM Handle'
-            def requestBody = '{"dmiPlugin":"'+DMI_URL+'", "removedCmHandles": ["ch-1", "ch-2", "ch-3"]}'
+            def requestBody = '{"dmiPlugin":"'+DMI1_URL+'", "removedCmHandles": ["ch-1", "ch-2", "ch-3"]}'
             mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody))
                     .andExpect(status().is2xxSuccessful())
         then: 'the CM handles are not found using GET'
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy
new file mode 100644 (file)
index 0000000..834e139
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.api.datajobs.DataJobService
+import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata
+import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest
+import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse
+import org.onap.cps.ncmp.api.datajobs.models.WriteOperation
+import org.springframework.beans.factory.annotation.Autowired
+
+class WriteSubJobSpec extends CpsIntegrationSpecBase {
+
+    @Autowired
+    DataJobService dataJobService
+
+    def setup() {
+        dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1']
+        dmiDispatcher1.moduleNamesPerCmHandleId['ch-2'] = ['M2']
+        dmiDispatcher2.moduleNamesPerCmHandleId['ch-3'] = ['M3']
+        registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'p1')
+        registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'p2')
+        registerCmHandle(DMI2_URL, 'ch-3', NO_MODULE_SET_TAG, 'p3')
+    }
+
+    def cleanup() {
+        deregisterCmHandle(DMI1_URL, 'ch-1')
+        deregisterCmHandle(DMI1_URL, 'ch-2')
+        deregisterCmHandle(DMI2_URL, 'ch-3')
+    }
+
+    def 'Create a sub-job write request.'() {
+        given: 'the required input data for the write job'
+            def authorization = 'my authorization header'
+            def dataJobWriteRequest = new DataJobWriteRequest([new WriteOperation('p1', '', '', null), new WriteOperation('p2', '', '', null), new WriteOperation('p3', '', '', null)])
+            def myDataJobMetadata = new DataJobMetadata('d1', '', '')
+            def dataJobId = 'my-data-job-id'
+        when: 'sending a write job to NCMP with 2 sub-jobs for DMI 1 and 1 sub-job for DMI 2'
+            def response = dataJobService.writeDataJob(authorization, dataJobId, myDataJobMetadata, dataJobWriteRequest)
+        then: 'each DMI received the expected sub-jobs and the response has the expected values'
+            assert response.size() == 2
+            assert response[0].class == SubJobWriteResponse.class
+            assert response[0].subJobId == "some sub job id"
+            assert response[0].dmiServiceName == "some dmi service name"
+            assert response[0].dataProducerId == "some data producer id"
+        and: 'dmi 1 received the correct job details'
+            def receivedSubJobsForDispatcher1 = dmiDispatcher1.receivedSubJobs['?destination=d1']['data'].collect()
+            assert receivedSubJobsForDispatcher1.size() == 2
+            assert receivedSubJobsForDispatcher1[0]['path'] == 'p1'
+            assert receivedSubJobsForDispatcher1[1]['path'] == 'p2'
+        and: 'dmi 2 received the correct job details'
+            def receivedSubJobsForDispatcher2 = dmiDispatcher2.receivedSubJobs['?destination=d1']['data'].collect()
+            assert receivedSubJobsForDispatcher2.size() == 1
+            assert receivedSubJobsForDispatcher2[0]['path'] == 'p3'
+    }
+}
index cbbf1d9..f6ae27d 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.integration.performance.base
 
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-
 import org.onap.cps.integration.ResourceMeter
 import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.utils.ContentType
+
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
 
 class NcmpPerfTestBase extends PerfTestBase {
 
@@ -79,7 +81,7 @@ class NcmpPerfTestBase extends PerfTestBase {
         def batchSize = 100
         for (def i = 0; i < TOTAL_CM_HANDLES; i += batchSize) {
             def data = '{ "cm-handles": [' + (1..batchSize).collect { innerNodeJsonTemplate.replace('CMHANDLE_ID_HERE', (it + i).toString()) }.join(',') + ']}'
-            cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/dmi-registry', data, now)
+            cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON)
         }
     }
 
@@ -91,7 +93,7 @@ class NcmpPerfTestBase extends PerfTestBase {
                 innerNodeJsonTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString())
                         .replace('ALTERNATE_ID_AS_PATH', (it + i).toString())
             }.join(',') + ']}'
-            cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry', data, now)
+            cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON)
         }
     }
 
index e123527..ab8ffe7 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ class CpsDataspaceServiceLimitsPerfTest extends CpsPerfTestBase {
         given: 'more than 32,766 schema set names'
             def schemaSetNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
         when: 'single get is executed to get all the anchors'
-            objectUnderTest.getAnchors(CPS_PERFORMANCE_TEST_DATASPACE, schemaSetNames)
+            objectUnderTest.getAnchorsBySchemaSetNames(CPS_PERFORMANCE_TEST_DATASPACE, schemaSetNames)
         then: 'a database exception is not thrown'
             noExceptionThrown()
     }
index 1d3943f..03abdb4 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -22,6 +23,7 @@ package org.onap.cps.integration.performance.cps
 
 import java.time.OffsetDateTime
 import org.onap.cps.api.CpsDataService
+import org.onap.cps.utils.ContentType
 import org.onap.cps.integration.performance.base.CpsPerfTestBase
 
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
@@ -48,7 +50,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
         given: 'replacement JSON for node containing list of device nodes'
             def jsonData = '{ "openroadm-devices": ' + generateJsonForOpenRoadmDevices(startId, totalNodes, changeLeaves) + '}'
         when: 'the container node is updated'
-            objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now)
+            objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now, ContentType.JSON)
         then: 'there are the expected number of total nodes'
             assert totalNodes == countDataNodes('/openroadm-devices/openroadm-device')
         where:
@@ -66,7 +68,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
             def jsonData = '{ "openroadm-devices": ' + generateJsonForOpenRoadmDevices(startId, totalNodes, changeLeaves) + '}'
         when: 'the container node is updated'
             resourceMeter.start()
-            objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now)
+            objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now, ContentType.JSON)
             resourceMeter.stop()
         then: 'there are the expected number of total nodes'
             assert totalNodes == countDataNodes('/openroadm-devices/openroadm-device')
@@ -121,7 +123,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
             def jsonDataUpdated  = "{'openroadm-device':[" + (1..100).collect {"{'device-id':'C201-7-1A-" + it + "','status':'fail','ne-state':'jeopardy'}" }.join(",") + "]}"
         when: 'update is performed for leaves'
             resourceMeter.start()
-            objectUnderTest.updateNodeLeaves(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, "/openroadm-devices", jsonDataUpdated, now)
+            objectUnderTest.updateNodeLeaves(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, "/openroadm-devices", jsonDataUpdated, now, ContentType.JSON)
             resourceMeter.stop()
         then: 'data leaves have expected values'
             assert 100 == countDataNodes('/openroadm-devices/openroadm-device[@status="fail"]')
index 0195611..9f6c78d 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -20,6 +21,8 @@
 
 package org.onap.cps.integration.performance.cps
 
+import org.onap.cps.utils.ContentType
+
 import java.time.OffsetDateTime
 import org.onap.cps.integration.performance.base.CpsPerfTestBase
 
@@ -87,7 +90,7 @@ class WritePerfTest extends CpsPerfTestBase {
                     ']}'
         when: 'device nodes are added'
             resourceMeter.start()
-            cpsDataService.saveListElements(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR, '/openroadm-devices', jsonListData, OffsetDateTime.now())
+            cpsDataService.saveListElements(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR, '/openroadm-devices', jsonListData, OffsetDateTime.now(), ContentType.JSON)
             resourceMeter.stop()
         then: 'the operation takes less than #expectedDuration and memory used is within limit'
             recordAndAssertResourceUsage("Saving list of ${totalNodes} devices",
index fc2f8cf..11f2a43 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2023 Nordix Foundation
+ *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -23,6 +24,7 @@ package org.onap.cps.integration.performance.ncmp
 import org.onap.cps.api.CpsQueryService
 import org.onap.cps.integration.performance.base.NcmpPerfTestBase
 import org.onap.cps.spi.model.DataNode
+import org.onap.cps.utils.ContentType
 
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 
@@ -86,7 +88,7 @@ class CmDataSubscriptionsPerfTest extends NcmpPerfTestBase {
                 // Around 8.5 seconds for long strings, 4.8 with short strings
                 // cpsDataService.updateDataNodeAndDescendants(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_ANCHOR, parentPath, json, now)
                 // Around 6.5 seconds for long strings, 3.3 seconds with short strings
-                cpsDataService.updateNodeLeaves(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_ANCHOR, parentPath, json, now)
+                cpsDataService.updateNodeLeaves(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_ANCHOR, parentPath, json, now, ContentType.JSON)
             }
 
             resourceMeter.stop()
index bbff5a8..a2d3a4a 100644 (file)
 
 package org.onap.cps.integration.performance.ncmp
 
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-
-import org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
 import org.onap.cps.integration.ResourceMeter
 import org.onap.cps.integration.performance.base.NcmpPerfTestBase
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+
 import java.util.stream.Collectors
 
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+
 class CmHandleQueryByAlternateIdPerfTest extends NcmpPerfTestBase {
 
-    InventoryPersistence objectUnderTest
+    AlternateIdMatcher objectUnderTest
     ResourceMeter resourceMeter = new ResourceMeter()
 
-    def setup() { objectUnderTest = inventoryPersistence }
+    def setup() { objectUnderTest = alternateIdMatcher }
 
     def 'Query cm handle by longest match alternate id'() {
         when: 'an alternate id as cps path query'
             resourceMeter.start()
             def cpsPath = "/a/b/c/d-5/e/f/g/h/i"
-            def dataNodes = objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(cpsPath, '/')
+            def dataNodes = objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(cpsPath, '/')
         and: 'the ids of the result are extracted and converted to xpath'
             def cpsXpaths = dataNodes.stream().map(dataNode -> "/dmi-registry/cm-handles[@id='${dataNode.leaves.id}']".toString() ).collect(Collectors.toSet())
         and: 'a single get is executed to get all the parent objects and their descendants'
-            cpsDataService.getDataNodesForMultipleXpaths(NcmpPersistence.NCMP_DATASPACE_NAME, NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR, cpsXpaths, OMIT_DESCENDANTS)
+            cpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsXpaths, OMIT_DESCENDANTS)
             resourceMeter.stop()
             def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
             print 'Total time in seconds to query ch handle by alternate id: ' + durationInSeconds
         then: 'the required operations are performed within required time and memory limit'
-            recordAndAssertResourceUsage('CpsPath Registry attributes Query', 1, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+            recordAndAssertResourceUsage('Look up cm-handle by longest match alternate-id', 1, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
     }
 }
index d518234..db0e24e 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.onap.cps.integration.performance.ncmp
 
-
 import org.onap.cps.api.CpsQueryService
 import org.onap.cps.integration.ResourceMeter
 import org.onap.cps.integration.performance.base.NcmpPerfTestBase
@@ -68,7 +67,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase {
             resourceMeter.stop()
             def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
         then: 'the required operations are performed within required time'
-            recordAndAssertResourceUsage("CpsPath Registry attributes Query", 3.96, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+            recordAndAssertResourceUsage("CpsPath Registry attributes Query", 3.96, durationInSeconds, 400, resourceMeter.getTotalMemoryUsageInMB())
         and: 'all nodes are returned'
             result.size() == TOTAL_CM_HANDLES
         and: 'the tree contains all the expected descendants too'
diff --git a/integration-test/src/test/java/org/onap/cps/integration/DmiStubTestContainer.java b/integration-test/src/test/java/org/onap/cps/integration/DmiStubTestContainer.java
new file mode 100644 (file)
index 0000000..1bffb35
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.integration;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+public class DmiStubTestContainer extends GenericContainer<DmiStubTestContainer> {
+
+    public static final String IMAGE_NAME_AND_VERSION =
+                                        "nexus3.onap.org:10003/onap/dmi-plugin-demo-and-csit-stub:latest";
+    public static final String DMI_STUB_URL = "http://localhost:8784";
+
+    private static DmiStubTestContainer dmiStubTestContainer;
+
+    private DmiStubTestContainer() {
+        super(DockerImageName.parse(IMAGE_NAME_AND_VERSION));
+    }
+
+    /**
+     * Provides an instance of the Dmi Plugin Stub test container wrapper.
+     * This will allow to interact with the DMI Stub in our acceptance tests.
+     *
+     * @return DmiStubTestContainer
+     */
+    public static DmiStubTestContainer getInstance() {
+        if (dmiStubTestContainer == null) {
+            dmiStubTestContainer = new DmiStubTestContainer();
+            dmiStubTestContainer.addFixedExposedPort(8784, 8092);
+            Runtime.getRuntime().addShutdownHook(new Thread(dmiStubTestContainer::close));
+        }
+        return dmiStubTestContainer;
+    }
+
+    @Override
+    public void start() {
+        super.start();
+    }
+
+    @Override
+    public void stop() {
+        // Method intentionally left blank
+    }
+}
index f8a2ecb..46bfcf6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@
 
 package org.onap.cps.integration;
 
+import static org.awaitility.Awaitility.await;
+
 import java.lang.management.GarbageCollectorMXBean;
 import java.lang.management.ManagementFactory;
 import java.lang.management.MemoryPoolMXBean;
@@ -71,7 +73,7 @@ public class ResourceMeter {
     static void performGcAndWait() {
         final long gcCountBefore = getGcCount();
         System.gc();
-        while (getGcCount() == gcCountBefore) {}
+        await().until(() -> getGcCount() > gcCountBefore);
     }
 
     private static long getGcCount() {
@@ -94,4 +96,3 @@ public class ResourceMeter {
                 .forEach(MemoryPoolMXBean::resetPeakUsage);
     }
 }
-
index 6fd3bca..760dad0 100644 (file)
@@ -97,10 +97,10 @@ app:
     async-m2m:
       topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
     avc:
-      subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:subscription}
-      subscription-forward-topic-prefix: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-}
-      subscription-response-topic: ${NCMP_RESPONSE_CM_AVC_SUBSCRIPTION:dmi-ncmp-cm-avc-subscription}
-      subscription-outcome-topic: ${NCMP_OUTCOME_CM_AVC_SUBSCRIPTION:subscription-response}
+      cm-subscription-ncmp-in: ${CM_SUBSCRIPTION_NCMP_IN_TOPIC:subscription}
+      cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription}
+      cm-subscription-dmi-out: ${CM_SUBSCRIPTION_DMI_OUT_TOPIC:dmi-ncmp-cm-avc-subscription}
+      cm-subscription-ncmp-out: ${CM_SUBSCRIPTION_NCMP_OUT_TOPIC:subscription-response}
       cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}
   lcm:
     events:
@@ -169,6 +169,7 @@ ncmp:
       maximumConnectionsPerRoute: 50
       maximumConnectionsTotal: 100
       idleConnectionEvictionThresholdInSeconds: 5
+      maximumInMemorySizeInMegabytes: 16
     auth:
       username: dmi
       password: dmi
@@ -178,9 +179,9 @@ ncmp:
 
   timers:
     advised-modules-sync:
-      sleep-time-ms: 100000
+      sleep-time-ms: 1000
     locked-modules-sync:
-      sleep-time-ms: 300000
+      sleep-time-ms: 1000
     cm-handle-data-sync:
       sleep-time-ms: 30000
     subscription-forwarding:
@@ -212,9 +213,29 @@ ncmp:
     init:
       mode: ALWAYS
 
+  policy-executor:
+    enabled: true
+    server:
+      address: http://localhost
+      port: 8790
+    httpclient:
+      all-services:
+        maximumInMemorySizeInMegabytes: 1
+        maximumConnectionsTotal: 10
+        pendingAcquireMaxCount: 10
+        connectionTimeoutInSeconds: 30
+        readTimeoutInSeconds: 30
+        writeTimeoutInSeconds: 30
+
 hazelcast:
   cluster-name: cps-and-ncmp-test-caches
   mode:
     kubernetes:
       enabled: false
       service-name: cps-and-ncmp-service
+
+cps:
+  tracing:
+    enabled: false
+    exporter:
+      protocol: grpc
index 447614f..503500f 100644 (file)
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
+        <version>3.5.3-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
             <artifactId>integration-test</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>policy-executor-stub</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/k6-tests/README.md b/k6-tests/README.md
new file mode 100644 (file)
index 0000000..9a385e1
--- /dev/null
@@ -0,0 +1,24 @@
+# k6 tests
+
+[k6](https://k6.io/) is used for performance tests.
+k6 tests are written in JavaScript.
+
+## k6 installation
+Follow the instructions in the [build from source guide](https://github.com/mostafa/xk6-kafka) to get started.
+
+## Running the k6 test suites
+Simply run the main script. (The script assumes k6 and docker-compose have been installed).
+```shell
+./run-k6-tests.sh
+```
+
+## Running k6 tests manually
+Before running tests, ensure CPS/NCMP is running:
+```shell
+docker-compose -f docker-compose/docker-compose.yml --profile dmi-stub up
+```
+
+To run an individual test from command line, use
+```shell
+k6 run ncmp/ncmp-kpi.js
+```
diff --git a/k6-tests/install-deps.sh b/k6-tests/install-deps.sh
new file mode 100755 (executable)
index 0000000..bb5deb9
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# Copyright 2024 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.
+#
+
+echo "---> install-deps.sh"
+echo "Installing dependencies"
+
+# Create directory for downloaded binaries.
+mkdir -p bin
+touch bin/.gitignore
+
+# Add it to the PATH, so downloaded versions will be used.
+export PATH="$(pwd)/bin:$PATH"
+
+# Download docker-compose.
+if [ ! -x bin/docker-compose ]; then
+  echo " Downloading docker-compose"
+  curl -s -L https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64 > bin/docker-compose
+  chmod +x bin/docker-compose
+else
+  echo " docker-compose already installed"
+fi
+docker-compose version
+
+# Download k6 with kafka extension.
+if [ ! -x bin/k6 ]; then
+  echo " Downloading k6 with kafka extension"
+  curl -s -L https://github.com/mostafa/xk6-kafka/releases/download/v0.26.0/xk6-kafka_v0.26.0_linux_amd64.tar.gz | tar -xz
+  mv dist/xk6-kafka_v0.26.0_linux_amd64 bin/k6 && rmdir dist
+  chmod +x bin/k6
+else
+  echo " k6 already installed"
+fi
+k6 --version
diff --git a/k6-tests/ncmp/common/cmhandle-crud.js b/k6-tests/ncmp/common/cmhandle-crud.js
new file mode 100644 (file)
index 0000000..7fab62a
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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=========================================================
+ */
+
+import { sleep } from 'k6';
+import { performPostRequest, NCMP_BASE_URL, DMI_PLUGIN_URL, TOTAL_CM_HANDLES, MODULE_SET_TAGS
+} from './utils.js';
+import { executeCmHandleIdSearch } from './search-base.js';
+
+export function createCmHandles(cmHandleIds) {
+    const url = `${NCMP_BASE_URL}/ncmpInventory/v1/ch`;
+    const payload = JSON.stringify(createCmHandlePayload(cmHandleIds));
+    return performPostRequest(url, payload, 'createCmHandles');
+}
+
+export function deleteCmHandles(cmHandleIds) {
+    const url = `${NCMP_BASE_URL}/ncmpInventory/v1/ch`;
+    const payload = JSON.stringify({
+        "dmiPlugin": DMI_PLUGIN_URL,
+        "removedCmHandles": cmHandleIds,
+    });
+    return performPostRequest(url, payload, 'deleteCmHandles');
+}
+
+export function waitForAllCmHandlesToBeReady() {
+    const POLLING_INTERVAL_SECONDS = 5;
+    let cmHandlesReady = 0;
+    do {
+        sleep(POLLING_INTERVAL_SECONDS);
+        cmHandlesReady = getNumberOfReadyCmHandles();
+        console.log(`${cmHandlesReady}/${TOTAL_CM_HANDLES} CM handles are READY`);
+    } while (cmHandlesReady < TOTAL_CM_HANDLES);
+}
+
+function createCmHandlePayload(cmHandleIds) {
+    return {
+        "dmiPlugin": DMI_PLUGIN_URL,
+        "createdCmHandles": cmHandleIds.map((cmHandleId, index) => ({
+            "cmHandle": cmHandleId,
+            "alternateId": cmHandleId.replace('ch-', 'alt-'),
+            "moduleSetTag": MODULE_SET_TAGS[index % MODULE_SET_TAGS.length],
+            "cmHandleProperties": {"neType": "RadioNode"},
+            "publicCmHandleProperties": {
+                "Color": "yellow",
+                "Size": "small",
+                "Shape": "cube"
+            }
+        })),
+    };
+}
+
+function getNumberOfReadyCmHandles() {
+    const response = executeCmHandleIdSearch('readyCmHandles');
+    const arrayOfCmHandleIds = JSON.parse(response.body);
+    return arrayOfCmHandleIds.length;
+}
diff --git a/k6-tests/ncmp/common/passthrough-crud.js b/k6-tests/ncmp/common/passthrough-crud.js
new file mode 100644 (file)
index 0000000..86fcef6
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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=========================================================
+ */
+
+import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
+import {
+    performPostRequest,
+    performGetRequest,
+    NCMP_BASE_URL,
+    LEGACY_BATCH_TOPIC_NAME,
+    TOTAL_CM_HANDLES,
+} from './utils.js';
+
+export function passthroughRead(useAlternateId) {
+    const cmHandleReference = getRandomCmHandleReference(useAlternateId);
+    const resourceIdentifier = 'my-resource-identifier';
+    const datastoreName = 'ncmp-datastore:passthrough-operational';
+    const includeDescendants = true;
+    const url = generatePassthroughUrl(cmHandleReference, datastoreName, resourceIdentifier, includeDescendants);
+    return performGetRequest(url, 'passthroughRead');
+}
+
+export function passthroughWrite(useAlternateId) {
+    const cmHandleReference = getRandomCmHandleReference(useAlternateId);
+    const resourceIdentifier = 'my-resource-identifier';
+    const datastoreName = 'ncmp-datastore:passthrough-running';
+    const includeDescendants = false;
+    const url = generatePassthroughUrl(cmHandleReference, datastoreName, resourceIdentifier, includeDescendants);
+    const payload = JSON.stringify({"neType": "BaseStation"});
+    return performPostRequest(url, payload, 'passthroughWrite');
+}
+
+export function legacyBatchRead(cmHandleIds) {
+    const url = `${NCMP_BASE_URL}/ncmp/v1/data?topic=${LEGACY_BATCH_TOPIC_NAME}`
+    const payload = JSON.stringify({
+        "operations": [
+            {
+                "resourceIdentifier": "parent/child",
+                "targetIds": cmHandleIds,
+                "datastore": "ncmp-datastore:passthrough-operational",
+                "options": "(fields=schemas/schema)",
+                "operationId": "12",
+                "operation": "read"
+            }
+        ]
+    });
+    return performPostRequest(url, payload, 'batchRead');
+}
+
+function getRandomCmHandleReference(useAlternateId) {
+    const prefix = useAlternateId ? 'alt' : 'ch';
+    return `${prefix}-${randomIntBetween(1, TOTAL_CM_HANDLES)}`;
+}
+
+function generatePassthroughUrl(cmHandleReference, datastoreName, resourceIdentifier, includeDescendants) {
+    const descendantsParam = includeDescendants ? `&include-descendants=${includeDescendants}` : '';
+    return `${NCMP_BASE_URL}/ncmp/v1/ch/${cmHandleReference}/data/ds/${datastoreName}?resourceIdentifier=${resourceIdentifier}${descendantsParam}`;
+}
\ No newline at end of file
diff --git a/k6-tests/ncmp/common/search-base.js b/k6-tests/ncmp/common/search-base.js
new file mode 100644 (file)
index 0000000..a6424fe
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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=========================================================
+ */
+
+import {performPostRequest, NCMP_BASE_URL} from './utils.js';
+
+export function executeCmHandleSearch(scenario) {
+    return executeSearchRequest('searches', scenario);
+}
+
+export function executeCmHandleIdSearch(scenario) {
+    return executeSearchRequest('id-searches', scenario);
+}
+
+function executeSearchRequest(searchType, scenario) {
+    const searchParameters = SEARCH_PARAMETERS_PER_SCENARIO[scenario];
+    const payload = JSON.stringify(searchParameters);
+    const url = `${NCMP_BASE_URL}/ncmp/v1/ch/${searchType}`;
+    return performPostRequest(url, payload, searchType);
+}
+
+const SEARCH_PARAMETERS_PER_SCENARIO = {
+    "module-and-properties": {
+        "cmHandleQueryParameters": [
+            {
+                "conditionName": "hasAllModules",
+                "conditionParameters": [{"moduleName": "ietf-yang-types"}]
+            },
+            {
+                "conditionName": "hasAllProperties",
+                "conditionParameters": [{"Color": "yellow"}]
+            }
+        ]
+    },
+    "readyCmHandles": {
+        "cmHandleQueryParameters": [
+            {
+                "conditionName": "cmHandleWithCpsPath",
+                "conditionParameters": [{"cpsPath": "//state[@cm-handle-state='READY']"}]
+            }
+        ]
+    }
+};
diff --git a/k6-tests/ncmp/common/utils.js b/k6-tests/ncmp/common/utils.js
new file mode 100644 (file)
index 0000000..8ee6d10
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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=========================================================
+ */
+
+import http from 'k6/http';
+export const NCMP_BASE_URL = 'http://localhost:8883';
+export const DMI_PLUGIN_URL = 'http://ncmp-dmi-plugin-demo-and-csit-stub:8092';
+export const TOTAL_CM_HANDLES = 20000;
+export const REGISTRATION_BATCH_SIZE = 100;
+export const READ_DATA_FOR_CM_HANDLE_DELAY_MS = 300; // must have same value as in docker-compose.yml
+export const WRITE_DATA_FOR_CM_HANDLE_DELAY_MS = 670; // must have same value as in docker-compose.yml
+export const CONTENT_TYPE_JSON_PARAM = {'Content-Type': 'application/json'};
+export const LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE = 200;
+export const LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS = 1000;
+export const LEGACY_BATCH_TOPIC_NAME = 'legacy_batch_topic';
+export const KAFKA_BOOTSTRAP_SERVERS = ['localhost:9092'];
+export const MODULE_SET_TAGS = ['tagA','tagB','tagC',' tagD']
+
+
+/**
+ * Generates a batch of CM-handle IDs based on batch size and number.
+ * @param {number} batchSize - Size of each batch.
+ * @param {number} batchNumber - Number of the batch.
+ * @returns {string[]} Array of CM-handle IDs, for example ['ch-201', 'ch-202' ... 'ch-300']
+ */
+export function makeBatchOfCmHandleIds(batchSize, batchNumber) {
+    const startIndex = 1 + batchNumber * batchSize;
+    return Array.from({ length: batchSize }, (_, i) => `ch-${startIndex + i}`);
+}
+
+/**
+ * Helper function to perform POST requests with JSON payload and content type.
+ * @param {string} url - The URL to send the POST request to.
+ * @param {Object} payload - The JSON payload to send in the POST request.
+ * @param {string} metricTag - A tag for the metric endpoint.
+ * @returns {Object} The response from the HTTP POST request.
+ */
+export function performPostRequest(url, payload, metricTag) {
+    const metricTags = {
+        endpoint: metricTag
+    };
+
+    return http.post(url, payload, {
+        headers: CONTENT_TYPE_JSON_PARAM,
+        tags: metricTags
+    });
+}
+
+/**
+ * Helper function to perform GET requests with metric tags.
+ *
+ * This function sends an HTTP GET request to the specified URL and attaches
+ * a metric tag to the request, which is useful for monitoring and analytics.
+ *
+ * @param {string} url - The URL to which the GET request will be sent.
+ * @param {string} metricTag - A string representing the metric tag to associate with the request.
+ *                             This tag is used for monitoring and tracking the request.
+ * @returns {Object} The response from the HTTP GET request. The response includes the status code,
+ *                   headers, body, and other related information.
+ */
+export function performGetRequest(url, metricTag) {
+    const metricTags = {
+        endpoint: metricTag
+    };
+    return http.get(url, {tags: metricTags});
+}
+
+export function makeCustomSummaryReport(testResults, scenarioConfig) {
+    const summaryCsvLines = [
+        '#,Test Name,Unit,Fs Requirement,Current Expectation,Actual',
+        makeSummaryCsvLine('0', 'HTTP request failures for all tests', 'rate of failed requests', 'http_req_failed', 0, testResults, scenarioConfig),
+        makeSummaryCsvLine('1', 'Registration of CM-handles', 'CM-handles/second', 'cmhandles_created_per_second', 110, testResults, scenarioConfig),
+        makeSummaryCsvLine('2', 'De-registration of CM-handles', 'CM-handles/second', 'cmhandles_deleted_per_second', 80, testResults, scenarioConfig),
+        makeSummaryCsvLine('3', 'CM-handle ID search with Module and Property filter', 'milliseconds', 'id_search_duration', 4000, testResults, scenarioConfig),
+        makeSummaryCsvLine('4', 'CM-handle search with Module and Property filter', 'milliseconds', 'cm_search_duration', 30000, testResults, scenarioConfig),
+        makeSummaryCsvLine('5a', 'NCMP overhead for Synchronous single CM-handle pass-through read', 'milliseconds', 'ncmp_overhead_passthrough_read', 40, testResults, scenarioConfig),
+        makeSummaryCsvLine('5b', 'NCMP overhead for Synchronous single CM-handle pass-through read with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_read_alt_id', 60, testResults, scenarioConfig),
+        makeSummaryCsvLine('6a', 'NCMP overhead for Synchronous single CM-handle pass-through write', 'milliseconds', 'ncmp_overhead_passthrough_write', 30, testResults, scenarioConfig),
+        makeSummaryCsvLine('6b', 'NCMP overhead for Synchronous single CM-handle pass-through write with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_write_alt_id', 60, testResults, scenarioConfig),
+        makeSummaryCsvLine('7', 'Legacy batch read operation', 'events/second', 'legacy_batch_read_cmhandles_per_second', 1100, testResults, scenarioConfig),
+    ];
+    return summaryCsvLines.join('\n') + '\n';
+}
+
+function makeSummaryCsvLine(testCase, testName, unit, measurementName, currentExpectation, testResults, scenarioConfig) {
+    const thresholdArray = JSON.parse(JSON.stringify(scenarioConfig.thresholds[measurementName]));
+    const thresholdString = thresholdArray[0];
+    const [thresholdKey, thresholdOperator, thresholdValue] = thresholdString.split(/\s+/);
+    const actualValue = testResults.metrics[measurementName].values[thresholdKey].toFixed(3);
+    return `${testCase},${testName},${unit},${thresholdValue},${currentExpectation},${actualValue}`;
+}
diff --git a/k6-tests/ncmp/ncmp-kpi.js b/k6-tests/ncmp/ncmp-kpi.js
new file mode 100644 (file)
index 0000000..f9a19c3
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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=========================================================
+ */
+
+import { check } from 'k6';
+import { Trend } from 'k6/metrics';
+import { Reader } from 'k6/x/kafka';
+import {
+    TOTAL_CM_HANDLES, READ_DATA_FOR_CM_HANDLE_DELAY_MS, WRITE_DATA_FOR_CM_HANDLE_DELAY_MS,
+    makeCustomSummaryReport, makeBatchOfCmHandleIds, LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE,
+    LEGACY_BATCH_TOPIC_NAME, KAFKA_BOOTSTRAP_SERVERS, REGISTRATION_BATCH_SIZE,
+    LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS
+} from './common/utils.js';
+import { createCmHandles, deleteCmHandles, waitForAllCmHandlesToBeReady } from './common/cmhandle-crud.js';
+import { executeCmHandleSearch, executeCmHandleIdSearch } from './common/search-base.js';
+import { passthroughRead, passthroughWrite, legacyBatchRead } from './common/passthrough-crud.js';
+
+let cmHandlesCreatedPerSecondTrend = new Trend('cmhandles_created_per_second', false);
+let cmHandlesDeletedPerSecondTrend = new Trend('cmhandles_deleted_per_second', false);
+let passthroughReadNcmpOverheadTrend = new Trend('ncmp_overhead_passthrough_read', true);
+let passthroughReadNcmpOverheadTrendWithAlternateId = new Trend('ncmp_overhead_passthrough_read_alt_id', true);
+let passthroughWriteNcmpOverheadTrend = new Trend('ncmp_overhead_passthrough_write', true);
+let passthroughWriteNcmpOverheadTrendWithAlternateId = new Trend('ncmp_overhead_passthrough_write_alt_id', true);
+let idSearchDurationTrend = new Trend('id_search_duration', true);
+let cmSearchDurationTrend = new Trend('cm_search_duration', true);
+let legacyBatchReadCmHandlesPerSecondTrend = new Trend('legacy_batch_read_cmhandles_per_second', false);
+
+const legacyBatchEventReader = new Reader({
+    brokers: KAFKA_BOOTSTRAP_SERVERS,
+    topic: LEGACY_BATCH_TOPIC_NAME,
+});
+
+const DURATION = '15m';
+const LEGACY_BATCH_THROUGHPUT_TEST_START_TIME = '15m30s';
+
+export const options = {
+    setupTimeout: '8m',
+    teardownTimeout: '6m',
+    scenarios: {
+        passthrough_read_scenario: {
+            executor: 'constant-vus',
+            exec: 'passthroughReadScenario',
+            vus: 4,
+            duration: DURATION,
+        },
+        passthrough_read_alt_id_scenario: {
+            executor: 'constant-vus',
+            exec: 'passthroughReadAltIdScenario',
+            vus: 4,
+            duration: DURATION,
+        },
+        passthrough_write_scenario: {
+            executor: 'constant-vus',
+            exec: 'passthroughWriteScenario',
+            vus: 4,
+            duration: DURATION,
+        },
+        passthrough_write_alt_id_scenario: {
+            executor: 'constant-vus',
+            exec: 'passthroughWriteAltIdScenario',
+            vus: 4,
+            duration: DURATION,
+        },
+        cm_handle_id_search_scenario: {
+            executor: 'constant-vus',
+            exec: 'cmHandleIdSearchScenario',
+            vus: 5,
+            duration: DURATION,
+        },
+        cm_handle_search_scenario: {
+            executor: 'constant-vus',
+            exec: 'cmHandleSearchScenario',
+            vus: 5,
+            duration: DURATION,
+        },
+        legacy_batch_produce_scenario: {
+            executor: 'shared-iterations',
+            exec: 'legacyBatchProduceScenario',
+            vus: 2,
+            iterations: LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS,
+            startTime: LEGACY_BATCH_THROUGHPUT_TEST_START_TIME,
+        },
+        legacy_batch_consume_scenario: {
+            executor: 'per-vu-iterations',
+            exec: 'legacyBatchConsumeScenario',
+            vus: 1,
+            iterations: 1,
+            startTime: LEGACY_BATCH_THROUGHPUT_TEST_START_TIME,
+        }
+    },
+    thresholds: {
+        'http_req_failed': ['rate == 0'],
+        'cmhandles_created_per_second': ['avg >= 22'],
+        'cmhandles_deleted_per_second': ['avg >= 22'],
+        'ncmp_overhead_passthrough_read': ['avg <= 40'],
+        'ncmp_overhead_passthrough_write': ['avg <= 40'],
+        'ncmp_overhead_passthrough_read_alt_id': ['avg <= 40'],
+        'ncmp_overhead_passthrough_write_alt_id': ['avg <= 40'],
+        'id_search_duration': ['avg <= 2000'],
+        'cm_search_duration': ['avg <= 15000'],
+        'legacy_batch_read_cmhandles_per_second': ['avg >= 150'],
+    },
+};
+
+export function setup() {
+    const startTimeInMillis = Date.now();
+
+    const TOTAL_BATCHES = Math.ceil(TOTAL_CM_HANDLES / REGISTRATION_BATCH_SIZE);
+    for (let batchNumber = 0; batchNumber < TOTAL_BATCHES; batchNumber++) {
+        const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(REGISTRATION_BATCH_SIZE, batchNumber);
+        const response = createCmHandles(nextBatchOfCmHandleIds);
+        check(response, { 'create CM-handles status equals 200': (r) => r.status === 200 });
+    }
+
+    waitForAllCmHandlesToBeReady();
+
+    const endTimeInMillis = Date.now();
+    const totalRegistrationTimeInSeconds = (endTimeInMillis - startTimeInMillis) / 1000.0;
+
+    cmHandlesCreatedPerSecondTrend.add(TOTAL_CM_HANDLES / totalRegistrationTimeInSeconds);
+}
+
+export function teardown() {
+    const startTimeInMillis = Date.now();
+
+    let DEREGISTERED_CM_HANDLES = 0
+    const TOTAL_BATCHES = Math.ceil(TOTAL_CM_HANDLES / REGISTRATION_BATCH_SIZE);
+    for (let batchNumber = 0; batchNumber < TOTAL_BATCHES; batchNumber++) {
+        const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(REGISTRATION_BATCH_SIZE, batchNumber);
+        const response = deleteCmHandles(nextBatchOfCmHandleIds);
+        if (response.error_code === 0) {
+              DEREGISTERED_CM_HANDLES += REGISTRATION_BATCH_SIZE
+        }
+        check(response, { 'delete CM-handles status equals 200': (r) => r.status === 200 });
+    }
+
+    const endTimeInMillis = Date.now();
+    const totalDeregistrationTimeInSeconds = (endTimeInMillis - startTimeInMillis) / 1000.0;
+
+    cmHandlesDeletedPerSecondTrend.add(DEREGISTERED_CM_HANDLES / totalDeregistrationTimeInSeconds);
+}
+
+export function passthroughReadScenario() {
+    const response = passthroughRead(false);
+    if (check(response, { 'passthrough read status equals 200': (r) => r.status === 200 })) {
+        const overhead = response.timings.duration - READ_DATA_FOR_CM_HANDLE_DELAY_MS;
+        passthroughReadNcmpOverheadTrend.add(overhead);
+    }
+}
+
+export function passthroughReadAltIdScenario() {
+    const response = passthroughRead(true);
+    if (check(response, { 'passthrough read with alternate Id status equals 200': (r) => r.status === 200 })) {
+        const overhead = response.timings.duration - READ_DATA_FOR_CM_HANDLE_DELAY_MS;
+        passthroughReadNcmpOverheadTrendWithAlternateId.add(overhead);
+    }
+}
+
+export function passthroughWriteScenario() {
+    const response = passthroughWrite(false);
+    if (check(response, { 'passthrough write status equals 201': (r) => r.status === 201 })) {
+        const overhead = response.timings.duration - WRITE_DATA_FOR_CM_HANDLE_DELAY_MS;
+        passthroughWriteNcmpOverheadTrend.add(overhead);
+    }
+}
+
+export function passthroughWriteAltIdScenario() {
+    const response = passthroughWrite(true);
+    if (check(response, { 'passthrough write with alternate Id status equals 201': (r) => r.status === 201 })) {
+        const overhead = response.timings.duration - WRITE_DATA_FOR_CM_HANDLE_DELAY_MS;
+        passthroughWriteNcmpOverheadTrendWithAlternateId.add(overhead);
+    }
+}
+
+export function cmHandleIdSearchScenario() {
+    const response = executeCmHandleIdSearch('module-and-properties');
+    if (check(response, { 'CM handle ID search status equals 200': (r) => r.status === 200 })
+     && check(response, { 'CM handle ID search returned expected CM-handles': (r) => r.json('#') === TOTAL_CM_HANDLES })) {
+        idSearchDurationTrend.add(response.timings.duration);
+    }
+}
+
+export function cmHandleSearchScenario() {
+    const response = executeCmHandleSearch('module-and-properties');
+    if (check(response, { 'CM handle search status equals 200': (r) => r.status === 200 })
+     && check(response, { 'CM handle search returned expected CM-handles': (r) => r.json('#') === TOTAL_CM_HANDLES })) {
+        cmSearchDurationTrend.add(response.timings.duration);
+    }
+}
+
+export function legacyBatchProduceScenario() {
+    const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE, 0);
+    const response = legacyBatchRead(nextBatchOfCmHandleIds);
+    check(response, { 'data operation batch read status equals 200': (r) => r.status === 200 });
+}
+
+export function legacyBatchConsumeScenario() {
+    const TOTAL_MESSAGES_TO_CONSUME = LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS * LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE;
+    try {
+        let messagesConsumed = 0;
+        let startTime = Date.now();
+
+        while (messagesConsumed < TOTAL_MESSAGES_TO_CONSUME) {
+            let messages = legacyBatchEventReader.consume({ limit: 1000 });
+
+            if (messages.length > 0) {
+                messagesConsumed += messages.length;
+            }
+        }
+
+        let endTime = Date.now();
+        const timeToConsumeMessagesInSeconds = (endTime - startTime) / 1000.0;
+        legacyBatchReadCmHandlesPerSecondTrend.add(TOTAL_MESSAGES_TO_CONSUME / timeToConsumeMessagesInSeconds);
+    } catch (error) {
+        legacyBatchReadCmHandlesPerSecondTrend.add(0);
+        console.error(error);
+    }
+}
+
+export function handleSummary(data) {
+    return {
+        stdout: makeCustomSummaryReport(data, options),
+    };
+}
diff --git a/k6-tests/ncmp/run-all-tests.sh b/k6-tests/ncmp/run-all-tests.sh
new file mode 100755 (executable)
index 0000000..1fa661a
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/bash
+#
+# Copyright 2024 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.
+#
+
+pushd "$(dirname "$0")" >/dev/null || exit 1
+
+number_of_failures=0
+echo "Running K6 performance tests..."
+
+# Redirecting stderr to /dev/null to prevent large log files
+k6 --quiet run ncmp-kpi.js > summary.csv 2>/dev/null || ((number_of_failures++))
+
+if [ -f summary.csv ]; then
+
+  # Output raw CSV for plotting job
+  echo '-- BEGIN CSV REPORT'
+  cat summary.csv
+  echo '-- END CSV REPORT'
+  echo
+
+  # Output human-readable report
+  echo '####################################################################################################'
+  echo '##                  K 6   P E R F O R M A N C E   T E S T   R E S U L T S                         ##'
+  echo '####################################################################################################'
+  column -t -s, summary.csv
+  echo
+
+  # Clean up
+  rm -f summary.csv
+
+else
+  echo "Error: Failed to generate summary.csv" >&2
+  ((number_of_failures++))
+fi
+
+popd >/dev/null || exit 1
+
+echo "NCMP TEST FAILURES: $number_of_failures"
+exit $number_of_failures
diff --git a/k6-tests/run-k6-tests.sh b/k6-tests/run-k6-tests.sh
new file mode 100755 (executable)
index 0000000..b1ad389
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# Copyright 2024 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.
+#
+
+set -o errexit  # Exit on most errors
+set -o nounset  # Disallow expansion of unset variables
+set -o pipefail # Use last non-zero exit code in a pipeline
+#set -o xtrace   # Uncomment for debugging
+
+on_exit() {
+  rc=$?
+  ./teardown.sh
+  popd
+  echo "TEST FAILURES: $rc"
+  exit $rc
+}
+trap on_exit EXIT
+
+pushd "$(dirname "$0")" || exit 1
+
+# Install needed dependencies.
+source install-deps.sh
+
+# Run k6 test suite.
+./setup.sh
+./ncmp/run-all-tests.sh
+NCMP_RESULT=$?
+
+# Note that the final steps are done in on_exit function after this exit!
+exit $NCMP_RESULT
diff --git a/k6-tests/setup.sh b/k6-tests/setup.sh
new file mode 100755 (executable)
index 0000000..346b9c0
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright 2024 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.
+#
+
+docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub up -d
+
+echo "Waiting for CPS to start..."
+READY_MESSAGE="Processing module sync fetched 0 advised cm handles from DB"
+
+# Get the container IDs of the cps-and-ncmp replicas
+CONTAINER_IDS=$(docker ps --filter "name=cps-and-ncmp" --format "{{.ID}}")
+
+# Check the logs for each container
+for CONTAINER_ID in $CONTAINER_IDS; do
+    echo "Checking logs for container: $CONTAINER_ID"
+    docker logs "$CONTAINER_ID" -f | grep -m 1 "$READY_MESSAGE" >/dev/null && echo "CPS is ready in container: $CONTAINER_ID" || true
+done
diff --git a/k6-tests/teardown.sh b/k6-tests/teardown.sh
new file mode 100755 (executable)
index 0000000..7693dc0
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright 2024 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.
+#
+
+echo '================================== docker info =========================='
+docker ps -a
+
+echo 'Stopping, Removing containers and volumes...'
+docker_compose_cmd="docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub down --volumes"
+# Set an environment variable CLEAN_DOCKER_IMAGES=1 to also remove docker images when done (used on jenkins job)
+if [ "${CLEAN_DOCKER_IMAGES:-0}" -eq 1 ]; then
+  $docker_compose_cmd --rmi all
+else
+  $docker_compose_cmd
+fi
diff --git a/policy-executor-stub/pom.xml b/policy-executor-stub/pom.xml
new file mode 100644 (file)
index 0000000..35ff835
--- /dev/null
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.onap.cps</groupId>
+        <artifactId>cps-parent</artifactId>
+        <version>3.5.3-SNAPSHOT</version>
+        <relativePath>../cps-parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>policy-executor-stub</artifactId>
+
+    <properties>
+        <app>org.onap.cps.policyexecutor.stub.PolicyExecutorApplication</app>
+        <maven.build.timestamp.format>yyyyMMdd'T'HHmmss'Z'</maven.build.timestamp.format>
+        <base.image>${docker.pull.registry}/onap/integration-java17:12.0.0</base.image>
+        <image.name>policy-executor-stub</image.name>
+        <image.tag>${project.version}-${maven.build.timestamp}</image.tag>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- S P R I N G   D E P E N D E N C I E S -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jetty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!-- O P E N   A P I   D E P E N D E N C I E S -->
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+        </dependency>
+        <!-- T E S T   D E P E N D E N C I E S -->
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-json</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-spring</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>com.google.cloud.tools</groupId>
+                    <artifactId>jib-maven-plugin</artifactId>
+                    <configuration>
+                        <container>
+                            <mainClass>${app}</mainClass>
+                            <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
+                        </container>
+                        <from>
+                            <image>${base.image}</image>
+                        </from>
+                        <to>
+                            <tags>
+                                <tag>latest</tag>
+                            </tags>
+                            <image>${docker.push.registry}/onap/${image.name}:${image.tag}</image>
+                        </to>
+                    </configuration>
+                    <executions>
+                        <execution>
+                            <phase>package</phase>
+                            <id>build</id>
+                            <goals>
+                                <goal>dockerBuild</goal>
+                            </goals>
+                        </execution>
+                        <execution>
+                            <phase>deploy</phase>
+                            <id>buildAndPush</id>
+                            <goals>
+                                <goal>build</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <!-- Swagger code generation. -->
+            <plugin>
+                <groupId>org.openapitools</groupId>
+                <artifactId>openapi-generator-maven-plugin</artifactId>
+                <version>6.6.0</version>
+                <executions>
+                    <execution>
+                        <id>code-gen</id>
+                        <goals>
+                            <goal>generate</goal>
+                        </goals>
+                        <configuration>
+                            <inputSpec>${project.parent.basedir}/../docs/api/swagger/policy-executor/openapi.yaml</inputSpec>
+                            <modelPackage>org.onap.cps.policyexecutor.stub.model</modelPackage>
+                            <apiPackage>org.onap.cps.policyexecutor.stub.api</apiPackage>
+                            <generatorName>spring</generatorName>
+                            <generateSupportingFiles>false</generateSupportingFiles>
+                            <configOptions>
+                                <sourceFolder>src/gen/java</sourceFolder>
+                                <dateLibrary>java11</dateLibrary>
+                                <interfaceOnly>true</interfaceOnly>
+                                <useSpringBoot3>true</useSpringBoot3>
+                                <useTags>true</useTags>
+                                <openApiNullable>false</openApiNullable>
+                                <skipDefaultInterface>true</skipDefaultInterface>
+                            </configOptions>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>openapi-yaml-gen</id>
+                        <goals>
+                            <goal>generate</goal>
+                        </goals>
+                        <phase>compile</phase>
+                        <configuration>
+                            <inputSpec>${project.parent.basedir}/../docs/api/swagger/policy-executor/openapi.yaml</inputSpec>
+                            <generatorName>openapi-yaml</generatorName>
+                            <configOptions>
+                                <outputFile>openapi.yaml</outputFile>
+                            </configOptions>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.jsonschema2pojo</groupId>
+                <artifactId>jsonschema2pojo-maven-plugin</artifactId>
+                <configuration>
+                    <useJakartaValidation>true</useJakartaValidation>
+                    <sourceDirectory>${project.parent.basedir}/../docs/schemas/policy-executor</sourceDirectory>
+                    <targetPackage>org.onap.cps.policyexecutor.stub.model</targetPackage>
+                    <generateBuilders>true</generateBuilders>
+                    <serializable>true</serializable>
+                    <includeJsr303Annotations>true</includeJsr303Annotations>
+                </configuration>
+            </plugin>
+
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>docker</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>com.google.cloud.tools</groupId>
+                        <artifactId>jib-maven-plugin</artifactId>
+                        <version>3.3.2</version>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+
+</project>
@@ -1,6 +1,6 @@
 /*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.dmi.rest.stub;
+package org.onap.cps.policyexecutor.stub;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
 @SpringBootApplication
-public class DmiDemoApplication {
-
+public class PolicyExecutorApplication {
     public static void main(final String[] args) {
-        SpringApplication.run(DmiDemoApplication.class, args);
+        SpringApplication.run(PolicyExecutorApplication.class, args);
     }
 }
diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java
new file mode 100644 (file)
index 0000000..cdd26c9
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.policyexecutor.stub.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.policyexecutor.stub.api.PolicyExecutorApi;
+import org.onap.cps.policyexecutor.stub.model.NcmpDelete;
+import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest;
+import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse;
+import org.onap.cps.policyexecutor.stub.model.Request;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+@Slf4j
+public class PolicyExecutorStubController implements PolicyExecutorApi {
+
+    private final ObjectMapper objectMapper;
+    private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("(\\d{3})");
+    private int decisionCounter = 0;
+
+    @Override
+    public ResponseEntity<PolicyExecutionResponse> executePolicyAction(
+                                                     final String action,
+                                                     final PolicyExecutionRequest policyExecutionRequest,
+                                                     final String authorization) {
+        log.info("Stub Policy Executor Invoked (only supports 'delete' operations)");
+        if (policyExecutionRequest.getRequests().isEmpty()) {
+            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+        }
+        final Request firstRequest = policyExecutionRequest.getRequests().iterator().next();
+        log.info("1st Request Schema:{}", firstRequest.getSchema());
+        if (firstRequest.getSchema().contains("ncmp-delete-schema:1.0.0")) {
+            return handleNcmpDeleteSchema(firstRequest);
+        }
+        log.warn("This stub only supports 'delete' operations");
+        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+    }
+
+    private ResponseEntity<PolicyExecutionResponse> handleNcmpDeleteSchema(final Request request) {
+        final NcmpDelete ncmpDelete = objectMapper.convertValue(request.getData(), NcmpDelete.class);
+
+        final String targetIdentifier = ncmpDelete.getTargetIdentifier();
+
+        if (targetIdentifier == null) {
+            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+        }
+
+        final Matcher matcher = ERROR_CODE_PATTERN.matcher(targetIdentifier);
+        if (matcher.find()) {
+            final int errorCode = Integer.parseInt(matcher.group(1));
+            return new ResponseEntity<>(HttpStatusCode.valueOf(errorCode));
+        }
+
+        return createPolicyExecutionResponse(targetIdentifier);
+    }
+
+    private ResponseEntity<PolicyExecutionResponse> createPolicyExecutionResponse(final String targetIdentifier) {
+        final String decisionId = String.valueOf(++decisionCounter);
+        final String decision;
+        final String message;
+
+        if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("cps-is-great")) {
+            decision = "allow";
+            message = "All good";
+        } else {
+            decision = "deny";
+            message = "Only FDNs containing 'cps-is-great' are allowed";
+        }
+        log.info("Decision: {} ({})", decision, message);
+        final PolicyExecutionResponse policyExecutionResponse =
+            new PolicyExecutionResponse(decisionId, decision, message);
+
+        return ResponseEntity.ok(policyExecutionResponse);
+    }
+
+}
diff --git a/policy-executor-stub/src/main/resources/application.yml b/policy-executor-stub/src/main/resources/application.yml
new file mode 100644 (file)
index 0000000..8ed5742
--- /dev/null
@@ -0,0 +1,19 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2024 Nordix
+# ================================================================================
+# 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=========================================================
+
+server:
+    port: 8093
diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/PolicyExecutorApplicationSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/PolicyExecutorApplicationSpec.groovy
new file mode 100644 (file)
index 0000000..565932d
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.policyexecutor.stub
+
+import spock.lang.Specification
+
+class PolicyExecutorApplicationSpec extends Specification {
+
+    def 'Execute Policy Action.'() {
+        when: 'Starting the application (for coverage)'
+            PolicyExecutorApplication.main()
+        then: 'all goes well'
+            noExceptionThrown()
+    }
+
+}
diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy
new file mode 100644 (file)
index 0000000..064e023
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 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.policyexecutor.stub.controller
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.policyexecutor.stub.model.NcmpDelete
+import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest
+import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse
+import org.onap.cps.policyexecutor.stub.model.Request
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.MockMvc
+import spock.lang.Specification
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+
+@WebMvcTest(PolicyExecutorStubController)
+class PolicyExecutorStubControllerSpec extends Specification {
+
+    @Autowired
+    MockMvc mockMvc
+
+    @Autowired
+    ObjectMapper objectMapper
+
+    def url = '/policy-executor/api/v1/some-action'
+
+    def 'Execute policy action.'() {
+        given: 'a policy execution request with target: #targetIdentifier'
+            def requestBody = createRequestBody(targetIdentifier)
+        when: 'request is posted'
+            def response = mockMvc.perform(post(url)
+                    .header('Authorization','some string')
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content(requestBody))
+                    .andReturn().response
+        then: 'response status is Ok'
+            assert response.status == HttpStatus.OK.value()
+        and: 'the response body has the expected decision details'
+            def responseBody = response.contentAsString
+            def policyExecutionResponse = objectMapper.readValue(responseBody, PolicyExecutionResponse.class)
+            assert policyExecutionResponse.decisionId == expectedDecsisonId
+            assert policyExecutionResponse.decision == expectedDecision
+            assert policyExecutionResponse.message == expectedMessage
+        where: 'the following targets are used'
+            targetIdentifier        || expectedDecsisonId | expectedDecision | expectedMessage
+            'some fdn'              || '1'                | 'deny'           | "Only FDNs containing 'cps-is-great' are allowed"
+            'fdn with cps-is-great' || '2'                | 'allow'          | 'All good'
+    }
+
+    def 'Execute policy action with a HTTP error code.'() {
+        given: 'a policy execution request with a target fdn with a 3-digit error code'
+            def requestBody = createRequestBody('target with error code 418')
+        when: 'request is posted'
+            def response = mockMvc.perform(post(url)
+                .header('Authorization','some string')
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(requestBody))
+                .andReturn().response
+        then: 'response status the same error code as in target fdn'
+            assert response.status == 418
+    }
+
+    def 'Execute policy action without authorization header.'() {
+        given: 'a valid policy execution request'
+            def requestBody = createRequestBody('some target')
+        when: 'request is posted without authorization header'
+            def response = mockMvc.perform(post(url)
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(requestBody))
+                .andReturn().response
+        then: 'response status is OK'
+            assert response.status == HttpStatus.OK.value()
+    }
+
+    def 'Execute policy action with no requests.'() {
+        given: 'a policy execution request'
+            def policyExecutionRequest = new PolicyExecutionRequest('some decision type', [])
+            def requestBody = objectMapper.writeValueAsString(policyExecutionRequest)
+        when: 'request is posted'
+            def response = mockMvc.perform(post(url)
+                .header('Authorization','some string')
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(requestBody))
+                .andReturn().response
+        then: 'response status is Bad Request'
+            assert response.status == HttpStatus.BAD_REQUEST.value()
+    }
+
+    def 'Execute policy action with invalid json for request data.'() {
+        when: 'request is posted'
+            def response = mockMvc.perform(post(url)
+                .header('Authorization','some string')
+                .contentType(MediaType.APPLICATION_JSON)
+                .content('invalid json'))
+                .andReturn().response
+        then: 'response status is Bad Request'
+            assert response.status == HttpStatus.BAD_REQUEST.value()
+    }
+
+    def 'Execute policy action with missing or invalid attributes.'() {
+        given: 'a policy execution request with decisionType=#decisionType, schema=#schema, targetIdentifier=#targetIdentifier'
+            def requestBody = createRequestBody(decisionType, schema, targetIdentifier)
+        when: 'request is posted'
+            def response = mockMvc.perform(post(url)
+                .header('Authorization','something')
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(requestBody))
+                .andReturn().response
+        then: 'response status as expected'
+            assert response.status == expectedStatus.value()
+        where: 'following parameters are used'
+            decisionType | schema                     | targetIdentifier || expectedStatus
+            'something'  | 'ncmp-delete-schema:1.0.0' | 'something'      || HttpStatus.OK
+            null         | 'ncmp-delete-schema:1.0.0' | 'something'      || HttpStatus.BAD_REQUEST
+            'something'  | 'other schema'             | 'something'      || HttpStatus.BAD_REQUEST
+            'something'  | 'ncmp-delete-schema:1.0.0' | null             || HttpStatus.BAD_REQUEST
+    }
+
+    def createRequestBody(decisionType, schema, targetIdentifier) {
+        def ncmpDelete = new NcmpDelete(targetIdentifier: targetIdentifier)
+        def request = new Request(schema, ncmpDelete)
+        def policyExecutionRequest = new PolicyExecutionRequest(decisionType, [request])
+        return objectMapper.writeValueAsString(policyExecutionRequest)
+    }
+
+    def createRequestBody(targetIdentifier) {
+        return createRequestBody('some decision type', 'ncmp-delete-schema:1.0.0', targetIdentifier)
+    }
+
+}
diff --git a/pom.xml b/pom.xml
index 4d04602..8347b1f 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
 \r
     <groupId>org.onap.cps</groupId>\r
     <artifactId>cps-aggregator</artifactId>\r
-    <version>3.5.0-SNAPSHOT</version>\r
+    <version>3.5.3-SNAPSHOT</version>\r
     <packaging>pom</packaging>\r
 \r
     <name>cps</name>\r
         <module>cps-ncmp-rest-stub</module>\r
         <module>cps-path-parser</module>\r
         <module>cps-ri</module>\r
-        <module>dmi-plugin-demo-and-csit-stub</module>\r
         <module>integration-test</module>\r
         <module>checkstyle</module>\r
         <module>spotbugs</module>\r
         <module>cps-application</module>\r
         <module>jacoco-report</module>\r
+        <module>policy-executor-stub</module>\r
     </modules>\r
 \r
     <build>\r
index 84dcb64..7ee9c67 100644 (file)
@@ -1,5 +1,5 @@
 {
-    "id": "e8e90dd2-20be-49be-813f-07db44c6a4c2",
+    "id": "fd705c16-c17e-434d-ad2e-ba040a7ed062",
     "name": "CPS Environment",
     "values": [
         {
         },
         {
             "key": "DMI_HOST",
-            "value": "localhost",
+            "value": "ncmp-dmi-plugin-demo-and-csit-stub",
             "type": "default",
             "enabled": true
         },
         {
             "key": "DMI_PORT",
-            "value": "8784",
-            "type": "default",
-            "enabled": true
-        },
-        {
-            "key": "SDNC_HOST",
-            "value": "localhost",
-            "type": "default",
-            "enabled": true
-        },
-        {
-            "key": "SDNC_PORT",
-            "value": "8282",
+            "value": "8092",
             "type": "default",
             "enabled": true
         }
     ],
     "_postman_variable_scope": "environment",
-    "_postman_exported_at": "2024-02-23T13:00:33.537Z",
-    "_postman_exported_using": "Postman/10.23.4"
-}
\ No newline at end of file
+    "_postman_exported_at": "2024-07-30T15:54:00.831Z",
+    "_postman_exported_using": "Postman/11.5.1"
+}
diff --git a/postman-collections/DMI Stub.postman_collection.json b/postman-collections/DMI Stub.postman_collection.json
deleted file mode 100644 (file)
index fe7250f..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-{
-    "info": {
-        "_postman_id": "4baf7902-0f1e-49a9-9c6a-f68f412240af",
-        "name": "DMI Stub",
-        "description": "A collection of the DMI Stub endpoints.",
-        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
-        "_exporter_id": "17907116"
-    },
-    "item": [
-        {
-            "name": "Execute a data operation for group of cm handle ids by supplied operation details",
-            "request": {
-                "method": "POST",
-                "header": [],
-                "body": {
-                    "mode": "raw",
-                    "raw": "{ \"operations\":\n    [\n        {\n            \"resourceIdentifier\": \"some resource identifier\",\n            \"datastore\": \"ncmp-datastore:passthrough-operational\",\n            \"options\": \"some option\",\n            \"operationId\": \"12\",\n            \"cmHandles\": [\n                {\n                    \"id\": \"cmHandle123\",\n                    \"cmHandleProperties\": {\n                        \"myProp\": \"some value\",\n                        \"otherProp\": \"other value\"\n                    }\n                },\n                {\n                    \"id\": \"cmHandle123\",\n                    \"cmHandleProperties\": {\n                        \"myProp\": \"some value\",\n                        \"otherProp\": \"other value\"\n                    }\n                }\n            ],\n            \"operation\": \"read\"\n        }\n    ]\n}",
-                    "options": {
-                        "raw": {
-                            "language": "json"
-                        }
-                    }
-                },
-                "url": {
-                    "raw": "http://{{DMI_HOST}}:{{DMI_PORT}}/dmi/v1/data?topic=ncmp-async-m2m&requestId=4753fc1f-7de2-449a-b306-a6204b5370b33",
-                    "protocol": "http",
-                    "host": [
-                        "{{DMI_HOST}}"
-                    ],
-                    "port": "{{DMI_PORT}}",
-                    "path": [
-                        "dmi",
-                        "v1",
-                        "data"
-                    ],
-                    "query": [
-                        {
-                            "key": "topic",
-                            "value": "ncmp-async-m2m"
-                        },
-                        {
-                            "key": "requestId",
-                            "value": "4753fc1f-7de2-449a-b306-a6204b5370b33"
-                        }
-                    ]
-                }
-            },
-            "response": []
-        },
-        {
-            "name": "Retrieve module resources for one or more modules",
-            "request": {
-                "method": "POST",
-                "header": [],
-                "body": {
-                    "mode": "raw",
-                    "raw": "{}",
-                    "options": {
-                        "raw": {
-                            "language": "json"
-                        }
-                    }
-                },
-                "url": {
-                    "raw": "http://{{DMI_HOST}}:{{DMI_PORT}}/dmi/v1/ch/cm-bookStore/moduleResources",
-                    "protocol": "http",
-                    "host": [
-                        "{{DMI_HOST}}"
-                    ],
-                    "port": "{{DMI_PORT}}",
-                    "path": [
-                        "dmi",
-                        "v1",
-                        "ch",
-                        "cm-bookStore",
-                        "moduleResources"
-                    ]
-                }
-            },
-            "response": []
-        },
-        {
-            "name": "Get all modules for given cm handle",
-            "protocolProfileBehavior": {
-                "disabledSystemHeaders": {
-                    "accept": true
-                }
-            },
-            "request": {
-                "method": "POST",
-                "header": [
-                    {
-                        "key": "Accept",
-                        "value": "application/json",
-                        "type": "text"
-                    }
-                ],
-                "body": {
-                    "mode": "raw",
-                    "raw": "{}",
-                    "options": {
-                        "raw": {
-                            "language": "json"
-                        }
-                    }
-                },
-                "url": {
-                    "raw": "http://{{DMI_HOST}}:{{DMI_PORT}}/dmi/v1/ch/cm-bookStore/modules",
-                    "protocol": "http",
-                    "host": [
-                        "{{DMI_HOST}}"
-                    ],
-                    "port": "{{DMI_PORT}}",
-                    "path": [
-                        "dmi",
-                        "v1",
-                        "ch",
-                        "cm-bookStore",
-                        "modules"
-                    ]
-                }
-            },
-            "response": []
-        }
-    ],
-    "auth": {
-        "type": "basic",
-        "basic": [
-            {
-                "key": "password",
-                "value": "cpsr0cks!",
-                "type": "string"
-            },
-            {
-                "key": "username",
-                "value": "cpsuser",
-                "type": "string"
-            }
-        ]
-    },
-    "event": [
-        {
-            "listen": "prerequest",
-            "script": {
-                "type": "text/javascript",
-                "exec": [
-                    ""
-                ]
-            }
-        },
-        {
-            "listen": "test",
-            "script": {
-                "type": "text/javascript",
-                "exec": [
-                    ""
-                ]
-            }
-        }
-    ]
-}
\ No newline at end of file
diff --git a/releases/3.5.0-container.yaml b/releases/3.5.0-container.yaml
new file mode 100644 (file)
index 0000000..d6e3b63
--- /dev/null
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.5.0
+project: cps
+log_dir: cps-maven-docker-stage-master/942/
+ref: 59575279e36572be4386f1c644db52fcb570fc9e
+containers:
+  - name: 'cps-and-ncmp'
+    version: '3.5.0-20240620T123504Z'
\ No newline at end of file
diff --git a/releases/3.5.0.yaml b/releases/3.5.0.yaml
new file mode 100644 (file)
index 0000000..81dc746
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/950/
+project: cps
+version: 3.5.0
\ No newline at end of file
diff --git a/releases/3.5.1-container.yaml b/releases/3.5.1-container.yaml
new file mode 100644 (file)
index 0000000..ddd96ba
--- /dev/null
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.5.1
+project: cps
+log_dir: cps-maven-docker-stage-master/943/
+ref: 7afd73d95b2de559e728b08abb7f003e70664c44
+containers:
+  - name: 'cps-and-ncmp'
+    version: '3.5.1-20240715T100945Z'
\ No newline at end of file
diff --git a/releases/3.5.1.yaml b/releases/3.5.1.yaml
new file mode 100644 (file)
index 0000000..f265e33
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/951/
+project: cps
+version: 3.5.1
\ No newline at end of file
diff --git a/releases/3.5.2-container.yaml b/releases/3.5.2-container.yaml
new file mode 100644 (file)
index 0000000..a1344fa
--- /dev/null
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.5.2
+project: cps
+log_dir: cps-maven-docker-stage-master/945/
+ref: 9002b9f6d0d64106e1cbc03d603dc0429da8badd
+containers:
+  - name: 'cps-and-ncmp'
+    version: '3.5.2-20240821T145201Z'
diff --git a/releases/3.5.2.yaml b/releases/3.5.2.yaml
new file mode 100644 (file)
index 0000000..8467524
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/953/
+project: cps
+version: 3.5.2
index bab9551..1433bd3 100644 (file)
@@ -25,7 +25,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>spotbugs</artifactId>
-    <version>3.5.0-SNAPSHOT</version>
+    <version>3.5.3-SNAPSHOT</version>
 
     <properties>
         <nexusproxy>https://nexus.onap.org</nexusproxy>
index 78f61d2..23e62cd 100644 (file)
@@ -36,6 +36,7 @@
       <Bug pattern="NP_NULL_PARAM_DEREF" />
       <Bug pattern="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" />
       <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" />
+      <Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" />
 
       <!-- https://stackoverflow.com/a/34674776. Doesn't detect Lombok All Args Constructor variables being used with map get key and value, which can lead to spotbugs being detected
       on used fields -->
index 5fdc4e3..5b93875 100644 (file)
@@ -22,7 +22,7 @@
 
 major=3
 minor=5
-patch=0
+patch=3
 
 base_version=${major}.${minor}.${patch}