Merge "Add document for support for contains condition to cps-path"
authorLuke Gleeson <luke.gleeson@est.tech>
Fri, 28 Apr 2023 13:59:06 +0000 (13:59 +0000)
committerGerrit Code Review <gerrit@onap.org>
Fri, 28 Apr 2023 13:59:06 +0000 (13:59 +0000)
79 files changed:
cps-application/src/main/resources/application.yml
cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java
cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java [new file with mode: 0644]
cps-ncmp-rest/docs/openapi/components.yaml
cps-ncmp-rest/docs/openapi/ncmp.yml
cps-ncmp-rest/docs/openapi/openapi.yml
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/handlers/NcmpCachedResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalQueryHandler.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java [deleted file]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java [new file with mode: 0644]
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/handlers/NcmpDatastoreRequestHandlerFactorySpec.groovy
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ResponseTimeoutTask.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumer.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/executor/TaskExecutor.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DataStoreEnum.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java
cps-ncmp-service/src/main/resources/model/subscription.yang
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfigSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/SubscriptionEventMapperSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy
cps-ncmp-service/src/test/resources/application.yml
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepository.java [deleted file]
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java [deleted file]
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java
cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceRepository.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy
cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java
cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy
cps-service/src/test/resources/application.yml
dmi-plugin-stub/mappings/batchCmHandles.json
docs/cps-path.rst
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsAdminServiceLimits.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimits.groovy

index 0007bc8..1eb1c11 100644 (file)
-#  ============LICENSE_START=======================================================\r
-#  Copyright (C) 2021 Pantheon.tech\r
-#  Modifications Copyright (C) 2021-2022 Bell Canada\r
-#  Modifications Copyright (C) 2021-2023 Nordix Foundation\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
-server:\r
-    port: 8080\r
-\r
-rest:\r
-    api:\r
-        cps-base-path: /cps/api\r
-        ncmp-base-path: /ncmp\r
-        ncmp-inventory-base-path: /ncmpInventory\r
-\r
-spring:\r
-    main:\r
-        banner-mode: "off"\r
-    application:\r
-        name: "cps-application"\r
-    jpa:\r
-        show-sql: false\r
-        ddl-auto: create\r
-        open-in-view: false\r
-        properties:\r
-            hibernate:\r
-                enable_lazy_load_no_trans: true\r
-                dialect: org.hibernate.dialect.PostgreSQLDialect\r
-\r
-    datasource:\r
-        url: jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/cpsdb\r
-        username: ${DB_USERNAME}\r
-        password: ${DB_PASSWORD}\r
-        driverClassName: org.postgresql.Driver\r
-        hikari:\r
-            minimumIdle: 5\r
-            maximumPoolSize: 80\r
-            idleTimeout: 60000\r
-            connectionTimeout: 120000\r
-            leakDetectionThreshold: 30000\r
-            pool-name: CpsDatabasePool\r
-\r
-    cache:\r
-        type: caffeine\r
-        cache-names: yangSchema\r
-        caffeine:\r
-            spec: maximumSize=10000,expireAfterAccess=10m\r
-\r
-    liquibase:\r
-        change-log: classpath:changelog/changelog-master.yaml\r
-        labels: ${LIQUIBASE_LABELS}\r
-\r
-    servlet:\r
-        multipart:\r
-            enabled: true\r
-            max-file-size: 100MB\r
-            max-request-size: 100MB\r
-\r
-    kafka:\r
-        bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVER:localhost:9092}\r
-        security:\r
-            protocol: PLAINTEXT\r
-        producer:\r
-            value-serializer: org.springframework.kafka.support.serializer.JsonSerializer\r
-            client-id: cps-core\r
-        consumer:\r
-            group-id: ${NCMP_CONSUMER_GROUP_ID:ncmp-group}\r
-            key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer\r
-            value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer\r
-            properties:\r
-                spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer\r
-                spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer\r
-                spring.json.use.type.headers: false\r
-\r
-    jackson:\r
-        default-property-inclusion: NON_NULL\r
-        serialization:\r
-            FAIL_ON_EMPTY_BEANS: false\r
-    sql:\r
-        init:\r
-            mode: ALWAYS\r
-app:\r
-    ncmp:\r
-        async-m2m:\r
-            topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}\r
-        avc:\r
-            subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:cm-avc-subscription}\r
-            cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}\r
-    lcm:\r
-        events:\r
-            topic: ${LCM_EVENTS_TOPIC:ncmp-events}\r
-    dmi:\r
-        cm-events:\r
-            topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events}\r
-\r
-\r
-notification:\r
-    enabled: true\r
-    data-updated:\r
-        topic: ${CPS_CHANGE_EVENT_TOPIC:cps.data-updated-events}\r
-        filters:\r
-            enabled-dataspaces: ${NOTIFICATION_DATASPACE_FILTER_PATTERNS:""}\r
-    async:\r
-        executor:\r
-            core-pool-size: 2\r
-            max-pool-size: 10\r
-            queue-capacity: 500\r
-            wait-for-tasks-to-complete-on-shutdown: true\r
-            thread-name-prefix: Async-\r
-            time-out-value-in-ms: 2000\r
-\r
-springdoc:\r
-    swagger-ui:\r
-        disable-swagger-default-url: true\r
-        urlsPrimaryName: cps-core\r
-        urls:\r
-            - name: cps-core\r
-              url: /api-docs/cps-core/openapi.yaml\r
-            - name: cps-ncmp\r
-              url: /api-docs/cps-ncmp/openapi.yaml\r
-            - name: cps-ncmp-inventory\r
-              url: /api-docs/cps-ncmp/openapi-inventory.yaml\r
-\r
-\r
-security:\r
-    # comma-separated uri patterns which do not require authorization\r
-    permit-uri: /manage/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/**\r
-    auth:\r
-        username: ${CPS_USERNAME}\r
-        password: ${CPS_PASSWORD}\r
-\r
-# Actuator\r
-management:\r
-    server:\r
-        port: 8081\r
-    endpoints:\r
-        web:\r
-            base-path: /manage\r
-            exposure:\r
-                include: info,health,loggers,prometheus\r
-    endpoint:\r
-        health:\r
-            show-details: always\r
-            # kubernetes probes: liveness and readiness\r
-            probes:\r
-                enabled: true\r
-\r
-logging:\r
-    format: json\r
-    level:\r
-        org:\r
-            springframework: INFO\r
-            onap:\r
-                cps: INFO\r
-ncmp:\r
-    dmi:\r
-        auth:\r
-            username: ${DMI_USERNAME}\r
-            password: ${DMI_PASSWORD}\r
-        api:\r
-            base-path: dmi\r
-\r
-    timers:\r
-        advised-modules-sync:\r
-            sleep-time-ms: 5000\r
-        locked-modules-sync:\r
-            sleep-time-ms: 300000\r
-        cm-handle-data-sync:\r
-            sleep-time-ms: 30000\r
-\r
-    modules-sync-watchdog:\r
-        async-executor:\r
-            parallelism-level: 10\r
-\r
-    model-loader:\r
-        subscription: false
\ No newline at end of file
+#  ============LICENSE_START=======================================================
+#  Copyright (C) 2021 Pantheon.tech
+#  Modifications Copyright (C) 2021-2022 Bell Canada
+#  Modifications 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=========================================================
+
+server:
+    port: 8080
+
+rest:
+    api:
+        cps-base-path: /cps/api
+        ncmp-base-path: /ncmp
+        ncmp-inventory-base-path: /ncmpInventory
+
+spring:
+    main:
+        banner-mode: "off"
+    application:
+        name: "cps-application"
+    jpa:
+        show-sql: false
+        ddl-auto: create
+        open-in-view: false
+        properties:
+            hibernate:
+                enable_lazy_load_no_trans: true
+                dialect: org.hibernate.dialect.PostgreSQLDialect
+
+    datasource:
+        url: jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/cpsdb
+        username: ${DB_USERNAME}
+        password: ${DB_PASSWORD}
+        driverClassName: org.postgresql.Driver
+        hikari:
+            minimumIdle: 5
+            maximumPoolSize: 80
+            idleTimeout: 60000
+            connectionTimeout: 120000
+            leakDetectionThreshold: 30000
+            pool-name: CpsDatabasePool
+
+    cache:
+        type: caffeine
+        cache-names: yangSchema
+        caffeine:
+            spec: maximumSize=10000,expireAfterAccess=10m
+
+    liquibase:
+        change-log: classpath:changelog/changelog-master.yaml
+        labels: ${LIQUIBASE_LABELS}
+
+    servlet:
+        multipart:
+            enabled: true
+            max-file-size: 100MB
+            max-request-size: 100MB
+
+    kafka:
+        bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVER:localhost:9092}
+        security:
+            protocol: PLAINTEXT
+        producer:
+            value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
+            client-id: cps-core
+        consumer:
+            group-id: ${NCMP_CONSUMER_GROUP_ID:ncmp-group}
+            key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+            value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+            properties:
+                spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
+                spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
+                spring.json.use.type.headers: false
+
+    jackson:
+        default-property-inclusion: NON_NULL
+        serialization:
+            FAIL_ON_EMPTY_BEANS: false
+    sql:
+        init:
+            mode: ALWAYS
+app:
+    ncmp:
+        async-m2m:
+            topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
+        avc:
+            subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:cm-avc-subscription}
+            subscription-forward-topic: ${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:cm-avc-subscription-response}
+            cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}
+    lcm:
+        events:
+            topic: ${LCM_EVENTS_TOPIC:ncmp-events}
+    dmi:
+        cm-events:
+            topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events}
+
+
+notification:
+    enabled: true
+    data-updated:
+        topic: ${CPS_CHANGE_EVENT_TOPIC:cps.data-updated-events}
+        filters:
+            enabled-dataspaces: ${NOTIFICATION_DATASPACE_FILTER_PATTERNS:""}
+    async:
+        executor:
+            core-pool-size: 2
+            max-pool-size: 10
+            queue-capacity: 500
+            wait-for-tasks-to-complete-on-shutdown: true
+            thread-name-prefix: Async-
+            time-out-value-in-ms: 2000
+
+springdoc:
+    swagger-ui:
+        disable-swagger-default-url: true
+        urlsPrimaryName: cps-core
+        urls:
+            - name: cps-core
+              url: /api-docs/cps-core/openapi.yaml
+            - name: cps-ncmp
+              url: /api-docs/cps-ncmp/openapi.yaml
+            - name: cps-ncmp-inventory
+              url: /api-docs/cps-ncmp/openapi-inventory.yaml
+
+
+security:
+    # comma-separated uri patterns which do not require authorization
+    permit-uri: /manage/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/**
+    auth:
+        username: ${CPS_USERNAME}
+        password: ${CPS_PASSWORD}
+
+# Actuator
+management:
+    server:
+        port: 8081
+    endpoints:
+        web:
+            base-path: /manage
+            exposure:
+                include: info,health,loggers,prometheus
+    endpoint:
+        health:
+            show-details: always
+            # kubernetes probes: liveness and readiness
+            probes:
+                enabled: true
+
+logging:
+    format: json
+    level:
+        org:
+            springframework: INFO
+            onap:
+                cps: INFO
+ncmp:
+    dmi:
+        auth:
+            username: ${DMI_USERNAME}
+            password: ${DMI_PASSWORD}
+        api:
+            base-path: dmi
+
+    timers:
+        advised-modules-sync:
+            sleep-time-ms: 5000
+        locked-modules-sync:
+            sleep-time-ms: 300000
+        cm-handle-data-sync:
+            sleep-time-ms: 30000
+        subscription-forwarding:
+            dmi-response-timeout-ms: 30000
+        model-loader:
+            retry-time-ms: 1000
+
+    modules-sync-watchdog:
+        async-executor:
+            parallelism-level: 10
+
+    model-loader:
+        subscription: false
+        maximum-attempt-count: 20
+
+# Custom Hazelcast Config.
+hazelcast:
+    mode:
+        kubernetes:
+            enabled: ${HAZELCAST_MODE_KUBERNETES_ENABLED:false}
+            service-name: ${CPS_NCMP_SERVICE_NAME:"cps-and-ncmp-service"}
\ No newline at end of file
index 3990dd1..6d5ee8c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (c) 2022 Nordix Foundation
+ *  Modifications 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.
@@ -31,14 +31,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
 import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
-import org.onap.cps.ncmp.rest.model.RestModuleDefinition;
-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.stub.handlers.NetworkCmProxyApiStubDefaultImpl;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.http.HttpStatus;
@@ -49,9 +45,7 @@ import org.springframework.web.bind.annotation.RestController;
 @Slf4j
 @RestController
 @RequestMapping("${rest.api.ncmp-stub-base-path}")
-public class NetworkCmProxyStubController implements NetworkCmProxyApi {
-
-    public static final String ASYNC_REQUEST_ID = "requestId";
+public class NetworkCmProxyStubController implements NetworkCmProxyApiStubDefaultImpl {
 
     @Value("${stub.path}")
     private String pathToResponseFiles;
@@ -86,23 +80,6 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
-    @Override
-    public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName,
-                                                                     final String resourceIdentifier,
-                                                                     final String cmHandleId,
-                                                                     final Object body,
-                                                                     final String contentType) {
-        return new ResponseEntity<>(HttpStatus.CREATED);
-    }
-
-    @Override
-    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName,
-                                                                     final String cmHandleId,
-                                                                     final String resourceIdentifier,
-                                                                     final String contentType) {
-        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
-    }
-
     @Override
     public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles(
             final CmHandleQueryParameters cmHandleQueryParameters) {
@@ -120,78 +97,12 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
         return ResponseEntity.ok(restOutputCmHandles);
     }
 
-    @Override
-    public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId,
-                                                                    final Boolean dataSyncEnabled) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<List<String>> searchCmHandleIds(
-            final CmHandleQueryParameters cmHandleQueryParameters) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId(
-            final String cmHandleId) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(final String cmHandle) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitionsByCmHandleId(final String cmHandle) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandleId) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName,
-                                                                      final String resourceIdentifier,
-                                                                      final String cmHandleId,
-                                                                      final Object body,
-                                                                      final String contentType) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<Object> queryResourceDataForCmHandle(final String datastoreName,
-                                                               final String cmHandle,
-                                                               final String cpsPath,
-                                                               final String options,
-                                                               final String topic,
-                                                               final Boolean includeDescendants) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleId) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
-    @Override
-    public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName,
-                                                                       final String resourceIdentifier,
-                                                                       final String cmHandleId,
-                                                                       final Object body,
-                                                                       final String contentType) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
     private ResponseEntity<Map<String, Object>> populateAsyncResponse(final String topicParamInQuery) {
         final Map<String, Object> responseData;
-        if (topicParamInQuery != null) {
-            responseData = getAsyncResponseData();
-        } else {
+        if (topicParamInQuery == null) {
             responseData = null;
+        } else {
+            responseData = getAsyncResponseData();
         }
         return ResponseEntity.ok().body(responseData);
     }
diff --git a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java
new file mode 100644 (file)
index 0000000..6e28dbc
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ *  ============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.rest.stub.handlers;
+
+import java.util.List;
+import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
+import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
+import org.onap.cps.ncmp.rest.model.RestModuleDefinition;
+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.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+public interface NetworkCmProxyApiStubDefaultImpl extends NetworkCmProxyApi {
+
+    String ASYNC_REQUEST_ID = "requestId";
+
+    @Override
+    default ResponseEntity<Object> getResourceDataForCmHandle(final String datastoreName,
+                                                              final String cmHandle,
+                                                              final String resourceIdentifier,
+                                                              final String optionsParamInQuery,
+                                                              final String topicParamInQuery,
+                                                              final Boolean includeDescendants) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<Object> getResourceDataForCmHandleBatch(final String resourceIdentifier,
+                                                                   final String topicParamInQuery,
+                                                                   final String datastoreName,
+                                                                   final Object requestBody,
+                                                                   final String optionsParamInQuery,
+                                                                   final Boolean includeDescendants) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<List<RestOutputCmHandle>> searchCmHandles(
+            final CmHandleQueryParameters cmHandleQueryParameters) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName,
+                                                                      final String resourceIdentifier,
+                                                                      final String cmHandleId,
+                                                                      final Object requestBody,
+                                                                      final String contentType) {
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Override
+    default ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName,
+                                                                      final String cmHandleId,
+                                                                      final String resourceIdentifier,
+                                                                      final String contentType) {
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Override
+    default ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId,
+                                                                     final Boolean dataSyncEnabled) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId(
+            final String cmHandleId) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(final String cmHandle) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<List<RestModuleDefinition>> getModuleDefinitionsByCmHandleId(final String cmHandle) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandleId) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName,
+                                                                       final String resourceIdentifier,
+                                                                       final String cmHandleId,
+                                                                       final Object requestBody,
+                                                                       final String contentType) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<Object> queryResourceDataForCmHandle(final String datastoreName,
+                                                                final String cmHandle,
+                                                                final String cpsPath,
+                                                                final String optionsParamInQuery,
+                                                                final String topicParamInQuery,
+                                                                final Boolean includeDescendants) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleId) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    default ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName,
+                                                                        final String resourceIdentifier,
+                                                                        final String cmHandleId,
+                                                                        final Object requestBody,
+                                                                        final String contentType) {
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+}
index 6ca63c7..7fc1063 100644 (file)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2022 Nordix Foundation
+#  Copyright (C) 2021-2023 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2022 Bell Canada
 #  ================================================================================
@@ -522,6 +522,18 @@ components:
         sample 1:
           value:
             topic: my-topic-name
+    requiredTopicParamInQuery:
+      name: topic
+      in: query
+      description: mandatory topic parameter in query.
+      required: true
+      schema:
+        type: string
+      allowReserved: true
+      examples:
+        sample 1:
+          value:
+            topic: my-topic-name
     contentParamInHeader:
       name: Content-Type
       in: header
index 1f7cce9..2b70d94 100755 (executable)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2022 Nordix Foundation
+#  Copyright (C) 2021-2023 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2021-2022 Bell Canada
 #  ================================================================================
@@ -194,6 +194,43 @@ resourceDataForCmHandle:
       502:
         $ref: 'components.yaml#/components/responses/BadGateway'
 
+getResourceDataForCmHandleBatch:
+  post:
+    tags:
+      - network-cm-proxy
+    summary: Get resource data for batch 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.
+    operationId: getResourceDataForCmHandleBatch
+    parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
+      - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
+      - $ref: 'components.yaml#/components/parameters/optionsParamInQuery'
+      - $ref: 'components.yaml#/components/parameters/requiredTopicParamInQuery'
+      - $ref: 'components.yaml#/components/parameters/includeDescendantsOptionInQuery'
+    requestBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            type: object
+    responses:
+      200:
+        description: OK
+        content:
+          application/json:
+            schema:
+              type: object
+      400:
+        $ref: 'components.yaml#/components/responses/BadRequest'
+      401:
+        $ref: 'components.yaml#/components/responses/Unauthorized'
+      403:
+        $ref: 'components.yaml#/components/responses/Forbidden'
+      500:
+        $ref: 'components.yaml#/components/responses/InternalServerError'
+      502:
+        $ref: 'components.yaml#/components/responses/BadGateway'
+
 queryResourceDataForCmHandle:
   get:
     tags:
index ee29366..5b4c0d3 100755 (executable)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2022 Nordix Foundation
+#  Copyright (C) 2021-2023 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2021 Bell Canada
 #  ================================================================================
@@ -34,6 +34,9 @@ paths:
   /v1/ch/{cm-handle}/data/ds/{datastore-name}:
     $ref: 'ncmp.yml#/resourceDataForCmHandle'
 
+  /v1/batch/data/ds/{datastore-name}:
+    $ref: 'ncmp.yml#/getResourceDataForCmHandleBatch'
+
   /v1/ch/{cm-handle}/data/ds/{datastore-name}/query:
     $ref: 'ncmp.yml#/queryResourceDataForCmHandle'
 
index 4da251f..a8bc3ae 100755 (executable)
 
 package org.onap.cps.ncmp.rest.controller;
 
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE;
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE;
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH;
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE;
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE;
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE;
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH;
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE;
 
 import java.util.Collection;
 import java.util.List;
@@ -74,12 +74,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     /**
      * Get resource data from datastore.
      *
-     * @param datastoreName       name of the datastore
-     * @param cmHandle            cm handle identifier
-     * @param resourceIdentifier  resource identifier
-     * @param optionsParamInQuery options query parameter
-     * @param topicParamInQuery   topic query parameter
-     * @param includeDescendants  whether include descendants
+     * @param datastoreName              name of the datastore
+     * @param cmHandle                   cm handle identifier
+     * @param resourceIdentifier         resource identifier
+     * @param optionsParamInQuery        options query parameter
+     * @param topicParamInQuery          topic query parameter
+     * @param includeDescendantsAsObject whether include descendants
      * @return {@code ResponseEntity} response from dmi plugin
      */
 
@@ -89,25 +89,48 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                              final String resourceIdentifier,
                                                              final String optionsParamInQuery,
                                                              final String topicParamInQuery,
-                                                             final Boolean includeDescendants) {
+                                                             final Boolean includeDescendantsAsObject) {
 
         final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler =
-                ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
+                ncmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
                         DatastoreType.fromDatastoreName(datastoreName));
 
+        final boolean includeDescendants = toPrimitiveFlag(includeDescendantsAsObject);
+
         return ncmpDatastoreRequestHandler.executeRequest(cmHandle, resourceIdentifier,
                 optionsParamInQuery, topicParamInQuery, includeDescendants);
     }
 
+    @Override
+    public ResponseEntity<Object> getResourceDataForCmHandleBatch(final String resourceIdentifier,
+                                                                  final String topicParamInQuery,
+                                                                  final String datastoreName,
+                                                                  final Object requestBody,
+                                                                  final String optionsParamInQuery,
+                                                                  final Boolean includeDescendantsAsObject) {
+
+        final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler =
+                ncmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
+                        DatastoreType.fromDatastoreName(datastoreName));
+
+        final List<String> cmHandleIds = jsonObjectMapper.convertJsonString(jsonObjectMapper.asJsonString(requestBody),
+                List.class);
+
+        final boolean includeDescendants = toPrimitiveFlag(includeDescendantsAsObject);
+
+        return ncmpDatastoreRequestHandler.executeRequest(cmHandleIds, resourceIdentifier,
+                optionsParamInQuery, topicParamInQuery, includeDescendants);
+    }
+
     /**
      * Query resource data from datastore.
      *
-     * @param datastoreName       name of the datastore
-     * @param cmHandle            cm handle identifier
-     * @param cpsPath             CPS Path
-     * @param optionsParamInQuery options query parameter
-     * @param topicParamInQuery   topic query parameter
-     * @param includeDescendants  whether include descendants
+     * @param datastoreName              name of the datastore
+     * @param cmHandle                   cm handle identifier
+     * @param cpsPath                    CPS Path
+     * @param optionsParamInQuery        options query parameter
+     * @param topicParamInQuery          topic query parameter
+     * @param includeDescendantsAsObject whether include descendants
      * @return {@code ResponseEntity} response from dmi plugin
      */
 
@@ -117,13 +140,15 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                                final String cpsPath,
                                                                final String optionsParamInQuery,
                                                                final String topicParamInQuery,
-                                                               final Boolean includeDescendants) {
+                                                               final Boolean includeDescendantsAsObject) {
         validateDataStore(DatastoreType.OPERATIONAL, datastoreName);
-        final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler =
-            ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceQueryHandler();
+        final NcmpDatastoreRequestHandler ncmpCachedResourceRequestHandler =
+            ncmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
+                    DatastoreType.fromDatastoreName(datastoreName));
 
-        return ncmpDatastoreRequestHandler.executeRequest(cmHandle, cpsPath, optionsParamInQuery,
-            topicParamInQuery, includeDescendants);
+        final boolean includeDescendants = toPrimitiveFlag(includeDescendantsAsObject);
+
+        return ncmpCachedResourceRequestHandler.executeRequest(cmHandle, cpsPath, includeDescendants);
     }
 
     /**
@@ -367,5 +392,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
             throw new InvalidDatastoreException(requestedDatastoreName + " is not supported");
         }
     }
+
+    private static boolean toPrimitiveFlag(final Boolean includeDescendantsAsObject) {
+        if (includeDescendantsAsObject == null) {
+            return false;
+        }
+        return includeDescendantsAsObject.booleanValue();
+    }
 }
 
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
new file mode 100644 (file)
index 0000000..620f647
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import java.util.function.Supplier;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.onap.cps.ncmp.api.NetworkCmProxyQueryService;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.springframework.stereotype.Component;
+
+@RequiredArgsConstructor
+@Component
+public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandler {
+
+    @Setter
+    private String dataStoreName;
+    private final NetworkCmProxyQueryService networkCmProxyQueryService;
+
+    @Override
+    public Supplier<Object> getTaskSupplierForGetRequest(final String cmHandleId,
+                                                         final String resourceIdentifier,
+                                                         final String optionsParamInQuery,
+                                                         final String topicParamInQuery,
+                                                         final String requestId,
+                                                         final boolean includeDescendants) {
+
+        final FetchDescendantsOption fetchDescendantsOption =
+                TaskManagementDefaultHandler.getFetchDescendantsOption(includeDescendants);
+
+        return () -> networkCmProxyDataService.getResourceDataForCmHandle(dataStoreName, cmHandleId, resourceIdentifier,
+                fetchDescendantsOption);
+    }
+
+    /**
+     * Gets ncmp datastore query handler.
+     * Note. Currently only ncmp-datastore:operational supports query operations
+     * @return a ncmp datastore query handler.
+     */
+    @Override
+    public Supplier<Object> getTaskSupplierForQueryRequest(final String cmHandleId,
+                                                           final String resourceIdentifier,
+                                                           final boolean includeDescendants) {
+
+        final FetchDescendantsOption fetchDescendantsOption =
+                TaskManagementDefaultHandler.getFetchDescendantsOption(includeDescendants);
+
+        return () -> networkCmProxyQueryService.queryResourceDataOperational(cmHandleId, resourceIdentifier,
+                fetchDescendantsOption);
+    }
+
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalQueryHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalQueryHandler.java
deleted file mode 100644 (file)
index 0586d42..0000000
+++ /dev/null
@@ -1,56 +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.rest.controller.handlers;
-
-import java.util.function.Supplier;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.NetworkCmProxyQueryService;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-import org.onap.cps.spi.FetchDescendantsOption;
-
-@Slf4j
-public class NcmpDatastoreOperationalQueryHandler extends NcmpDatastoreRequestHandler {
-
-    private final NetworkCmProxyQueryService networkCmProxyQueryService;
-
-    public NcmpDatastoreOperationalQueryHandler(final NetworkCmProxyQueryService networkCmProxyQueryService,
-                                                final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
-                                                final int timeOutInMilliSeconds,
-                                                final boolean notificationFeatureEnabled) {
-        super(null, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
-        this.networkCmProxyQueryService = networkCmProxyQueryService;
-    }
-
-    @Override
-    public Supplier<Object> getTaskSupplier(final String cmHandle,
-                                            final String resourceIdentifier,
-                                            final String optionsParamInQuery,
-                                            final String topicParamInQuery,
-                                            final String requestId,
-                                            final Boolean includeDescendant) {
-
-        final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendant);
-
-        return () -> networkCmProxyQueryService.queryResourceDataOperational(cmHandle, resourceIdentifier,
-            fetchDescendantsOption);
-    }
-
-}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java
deleted file mode 100644 (file)
index a4720b2..0000000
+++ /dev/null
@@ -1,53 +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.rest.controller.handlers;
-
-import java.util.function.Supplier;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-import org.onap.cps.spi.FetchDescendantsOption;
-
-@Slf4j
-public class NcmpDatastoreOperationalResourceRequestHandler extends NcmpDatastoreRequestHandler {
-
-    public NcmpDatastoreOperationalResourceRequestHandler(final NetworkCmProxyDataService networkCmProxyDataService,
-                                                          final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
-                                                          final int timeOutInMilliSeconds,
-                                                          final boolean notificationFeatureEnabled) {
-        super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
-    }
-
-    @Override
-    public Supplier<Object> getTaskSupplier(final String cmHandle,
-                                            final String resourceIdentifier,
-                                            final String optionsParamInQuery,
-                                            final String topicParamInQuery,
-                                            final String requestId,
-                                            final Boolean includeDescendant) {
-
-        final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendant);
-
-        return () -> networkCmProxyDataService.getResourceDataOperational(cmHandle, resourceIdentifier,
-                fetchDescendantsOption);
-    }
-
-}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java
deleted file mode 100644 (file)
index 1445e3e..0000000
+++ /dev/null
@@ -1,51 +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.rest.controller.handlers;
-
-import java.util.function.Supplier;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-
-@Slf4j
-public class NcmpDatastorePassthroughOperationalResourceRequestHandler extends NcmpDatastoreRequestHandler {
-
-    public NcmpDatastorePassthroughOperationalResourceRequestHandler(
-            final NetworkCmProxyDataService networkCmProxyDataService,
-            final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
-            final int timeOutInMilliSeconds,
-            final boolean notificationFeatureEnabled) {
-        super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
-    }
-
-    @Override
-    public Supplier<Object> getTaskSupplier(final String cmHandle,
-                                            final String resourceIdentifier,
-                                            final String optionsParamInQuery,
-                                            final String topicParamInQuery,
-                                            final String requestId,
-                                            final Boolean includeDescendant) {
-
-        return () -> networkCmProxyDataService.getResourceDataOperationalForCmHandle(
-                cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
-    }
-
-}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java
deleted file mode 100644 (file)
index 8194ec9..0000000
+++ /dev/null
@@ -1,50 +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.rest.controller.handlers;
-
-import java.util.function.Supplier;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-
-@Slf4j
-public class NcmpDatastorePassthroughRunningResourceRequestHandler extends NcmpDatastoreRequestHandler {
-
-    public NcmpDatastorePassthroughRunningResourceRequestHandler(
-            final NetworkCmProxyDataService networkCmProxyDataService,
-            final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
-            final int timeOutInMilliSeconds,
-            final boolean notificationFeatureEnabled) {
-        super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
-    }
-
-    @Override
-    public Supplier<Object> getTaskSupplier(final String cmHandle,
-                                            final String resourceIdentifier,
-                                            final String optionsParamInQuery,
-                                            final String topicParamInQuery,
-                                            final String requestId,
-                                            final Boolean includeDescendant) {
-
-        return () -> networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(
-                cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
-    }
-}
index 8502003..050e724 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
 
 package org.onap.cps.ncmp.rest.controller.handlers;
 
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.function.Supplier;
-import lombok.RequiredArgsConstructor;
+import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
 import org.onap.cps.ncmp.rest.util.TopicValidator;
-import org.onap.cps.spi.FetchDescendantsOption;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
 
-@RequiredArgsConstructor
+@NoArgsConstructor
 @Slf4j
-public abstract class NcmpDatastoreRequestHandler {
+@Service
+public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler {
 
-    private static final String NO_REQUEST_ID = null;
-    private static final String NO_TOPIC = null;
-
-    protected final NetworkCmProxyDataService networkCmProxyDataService;
-    protected final CpsNcmpTaskExecutor cpsNcmpTaskExecutor;
-    protected final int timeOutInMilliSeconds;
-    protected final boolean notificationFeatureEnabled;
-
-    protected abstract Supplier<Object> getTaskSupplier(final String cmHandle,
-                                                        final String resourceIdentifier,
-                                                        final String optionsParamInQuery,
-                                                        final String topicParamInQuery,
-                                                        final String requestId,
-                                                        final Boolean includeDescendant);
+    @Value("${notification.async.executor.time-out-value-in-ms:2000}")
+    protected int timeOutInMilliSeconds;
+    @Value("${notification.enabled:true}")
+    protected boolean notificationFeatureEnabled;
+    @Autowired
+    protected NetworkCmProxyDataService networkCmProxyDataService;
+    @Autowired
+    protected CpsNcmpTaskExecutor cpsNcmpTaskExecutor;
 
     /**
-     * Execute a request on a datastore.
+     * Executes synchronous/asynchronous request for given cm handle.
      *
      * @param cmHandleId          the cm handle
      * @param resourceIdentifier  the resource identifier
@@ -64,25 +63,62 @@ public abstract class NcmpDatastoreRequestHandler {
                                                  final String resourceIdentifier,
                                                  final String optionsParamInQuery,
                                                  final String topicParamInQuery,
-                                                 final Boolean includeDescendants) {
+                                                 final boolean includeDescendants) {
 
         final boolean asyncResponseRequested = topicParamInQuery != null;
         if (asyncResponseRequested && notificationFeatureEnabled) {
-            final String requestId = UUID.randomUUID().toString();
-            final Supplier<Object> taskSupplier = getTaskSupplier(cmHandleId, resourceIdentifier, optionsParamInQuery,
-                topicParamInQuery, requestId, includeDescendants);
-            return executeTaskAsync(topicParamInQuery, requestId, taskSupplier);
+            return executeAsyncTaskAndGetResponseEntity(cmHandleId, resourceIdentifier, optionsParamInQuery,
+                    topicParamInQuery, includeDescendants, false);
         }
 
         if (asyncResponseRequested) {
             log.warn("Asynchronous request is unavailable as notification feature is currently disabled, "
-                + "will use synchronous operation.");
+                    + "will use synchronous operation.");
         }
-        final Supplier<Object> taskSupplier = getTaskSupplier(cmHandleId, resourceIdentifier, optionsParamInQuery,
-            NO_TOPIC, NO_REQUEST_ID, includeDescendants);
+        final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(cmHandleId,
+                resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID, includeDescendants);
         return executeTaskSync(taskSupplier);
     }
 
+    /**
+     * Executes a synchronous 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);
+    }
+
+    /**
+     * Executes synchronous/asynchronous request for batch of cm handles.
+     *
+     * @param cmHandleIds         list of cm handles
+     * @param resourceIdentifier  the resource identifier
+     * @param optionsParamInQuery the options param in query
+     * @param topicParamInQuery   the topic param in query
+     * @param includeDescendants  whether include descendants
+     * @return the response entity
+     */
+    public ResponseEntity<Object> executeRequest(final List<String> cmHandleIds,
+                                                 final String resourceIdentifier,
+                                                 final String optionsParamInQuery,
+                                                 final String topicParamInQuery,
+                                                 final boolean includeDescendants) {
+
+        return executeAsyncTaskAndGetResponseEntity(cmHandleIds, resourceIdentifier, optionsParamInQuery,
+                topicParamInQuery, includeDescendants, true);
+
+    }
+
     protected ResponseEntity<Object> executeTaskAsync(final String topicParamInQuery,
                                                       final String requestId,
                                                       final Supplier<Object> taskSupplier) {
@@ -98,9 +134,25 @@ public abstract class NcmpDatastoreRequestHandler {
         return ResponseEntity.ok(taskSupplier.get());
     }
 
-    protected static FetchDescendantsOption getFetchDescendantsOption(final Boolean includeDescendant) {
-        return Boolean.TRUE.equals(includeDescendant) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-            : FetchDescendantsOption.OMIT_DESCENDANTS;
+    private ResponseEntity<Object> executeAsyncTaskAndGetResponseEntity(final Object targetObject,
+                                                                        final String resourceIdentifier,
+                                                                        final String optionsParamInQuery,
+                                                                        final String topicParamInQuery,
+                                                                        final boolean includeDescendants,
+                                                                        final boolean isBulkRequest) {
+        final String requestId = UUID.randomUUID().toString();
+        final Supplier<Object> taskSupplier;
+        if (isBulkRequest) {
+            taskSupplier = getTaskSupplierForBulkRequest((List<String>) targetObject,
+                    resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, includeDescendants);
+        } else {
+            taskSupplier = getTaskSupplierForGetRequest(targetObject.toString(), resourceIdentifier,
+                    optionsParamInQuery, topicParamInQuery, requestId, includeDescendants);
+        }
+        if (taskSupplier == NO_OBJECT_SUPPLIER) {
+            return new ResponseEntity<>(Map.of("status", "Unable to execute request as "
+                    + "datastore is not implemented."), HttpStatus.NOT_IMPLEMENTED);
+        }
+        return executeTaskAsync(topicParamInQuery, requestId, taskSupplier);
     }
-
 }
index ff7bda6..9a71798 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
 package org.onap.cps.ncmp.rest.controller.handlers;
 
 import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.NetworkCmProxyQueryService;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 @Component
 @RequiredArgsConstructor
 public class NcmpDatastoreResourceRequestHandlerFactory {
-
-    private final NetworkCmProxyDataService networkCmProxyDataService;
-    private final NetworkCmProxyQueryService networkCmProxyQueryService;
-    private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor;
-
-    @Value("${notification.async.executor.time-out-value-in-ms:2000}")
-    private int timeOutInMilliSeconds;
-    @Value("${notification.enabled:true}")
-    private boolean notificationFeatureEnabled;
+    private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler;
+    private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler;
 
     /**
      * Gets ncmp datastore handler.
@@ -46,31 +35,17 @@ public class NcmpDatastoreResourceRequestHandlerFactory {
      * @param datastoreType the datastore type
      * @return the ncmp datastore handler
      */
-    public NcmpDatastoreRequestHandler getNcmpDatastoreResourceRequestHandler(
-            final DatastoreType datastoreType) {
+    public NcmpDatastoreRequestHandler getNcmpResourceRequestHandler(final DatastoreType datastoreType) {
 
         switch (datastoreType) {
             case OPERATIONAL:
-                return new NcmpDatastoreOperationalResourceRequestHandler(networkCmProxyDataService,
-                        cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+                ncmpCachedResourceRequestHandler.setDataStoreName(datastoreType.getDatastoreName());
+                return ncmpCachedResourceRequestHandler;
             case PASSTHROUGH_RUNNING:
-                return new NcmpDatastorePassthroughRunningResourceRequestHandler(networkCmProxyDataService,
-                        cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
             case PASSTHROUGH_OPERATIONAL:
             default:
-                return new NcmpDatastorePassthroughOperationalResourceRequestHandler(networkCmProxyDataService,
-                        cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+                ncmpPassthroughResourceRequestHandler.setDataStoreName(datastoreType.getDatastoreName());
+                return ncmpPassthroughResourceRequestHandler;
         }
     }
-
-    /**
-     * Gets ncmp datastore query handler.
-     * Note. Currently only ncmp-datastore:operational supports query operations
-     * @return a ncmp datastore query handler.
-     */
-    public NcmpDatastoreRequestHandler getNcmpDatastoreResourceQueryHandler() {
-        return new NcmpDatastoreOperationalQueryHandler(networkCmProxyQueryService, cpsNcmpTaskExecutor,
-            timeOutInMilliSeconds, notificationFeatureEnabled);
-    }
-
 }
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
new file mode 100644 (file)
index 0000000..ab5d587
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import java.util.List;
+import java.util.function.Supplier;
+import lombok.Setter;
+import org.springframework.stereotype.Component;
+
+@Component
+public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestHandler {
+
+    @Setter
+    private String dataStoreName;
+
+    @Override
+    public Supplier<Object> getTaskSupplierForGetRequest(final String cmHandleId,
+                                                         final String resourceIdentifier,
+                                                         final String optionsParamInQuery,
+                                                         final String topicParamInQuery,
+                                                         final String requestId,
+                                                         final boolean includeDescendants) {
+
+        return () -> networkCmProxyDataService.getResourceDataForCmHandle(
+                dataStoreName, cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
+    }
+
+    @Override
+    public Supplier<Object> getTaskSupplierForBulkRequest(final List<String> cmHandleIds,
+                                                         final String resourceIdentifier,
+                                                         final String optionsParamInQuery,
+                                                         final String topicParamInQuery,
+                                                         final String requestId,
+                                                         final boolean includeDescendants) {
+
+        return () -> networkCmProxyDataService.getResourceDataForCmHandleBatch(
+                dataStoreName, cmHandleIds, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
+    }
+
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java
new file mode 100644 (file)
index 0000000..08e8407
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import java.util.List;
+import java.util.function.Supplier;
+import org.onap.cps.spi.FetchDescendantsOption;
+
+public interface TaskManagementDefaultHandler {
+
+    String NO_REQUEST_ID = null;
+    String NO_TOPIC = null;
+    Supplier<Object> NO_OBJECT_SUPPLIER = null;
+
+    default Supplier<Object> getTaskSupplierForGetRequest(final String cmHandleId,
+                                                          final String resourceIdentifier,
+                                                          final String optionsParamInQuery,
+                                                          final String topicParamInQuery,
+                                                          final String requestId,
+                                                          final boolean includeDescendant) {
+        return NO_OBJECT_SUPPLIER;
+
+    }
+
+    default Supplier<Object> getTaskSupplierForQueryRequest(final String cmHandleId,
+                                                            final String resourceIdentifier,
+                                                            final boolean includeDescendant) {
+        return NO_OBJECT_SUPPLIER;
+
+    }
+
+    default Supplier<Object> getTaskSupplierForBulkRequest(final List<String> cmHandleIds,
+                                                           final String resourceIdentifier,
+                                                           final String optionsParamInQuery,
+                                                           final String topicParamInQuery,
+                                                           final String requestId,
+                                                           final boolean includeDescendant) {
+        return NO_OBJECT_SUPPLIER;
+    }
+
+    static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) {
+        return includeDescendants ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+                : FetchDescendantsOption.OMIT_DESCENDANTS;
+    }
+}
index d67804e..9531101 100644 (file)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021 highstreet technologies GmbH
- *  Modifications Copyright (C) 2021-2022 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,18 +33,13 @@ import org.onap.cps.ncmp.api.inventory.CompositeState
 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalQueryHandler
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalResourceRequestHandler
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughOperationalResourceRequestHandler
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughRunningResourceRequestHandler
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory
-import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
 import org.onap.cps.spi.FetchDescendantsOption
-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
@@ -55,31 +50,32 @@ 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 org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
 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.api.inventory.CompositeState.DataStores
 import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE
+import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.OPERATIONAL
 
 @WebMvcTest(NetworkCmProxyController)
 class NetworkCmProxyControllerSpec extends Specification {
 
-    public static final int TIMEOUT_IN_MS = 2000
-    public static final boolean NOTIFICATION_ENABLED = true
+    private static final int TIMEOUT_IN_MS = 2000
+    private static final boolean NOTIFICATION_ENABLED = true
 
     @Autowired
     MockMvc mvc
@@ -115,6 +111,7 @@ class NetworkCmProxyControllerSpec extends Specification {
     def ncmpBasePathV1
 
     def requestBody = '{"some-key":"some-value"}'
+    def bulkRequestBody = '["testCmHandle"]'
 
     @Shared
     def NO_TOPIC = null
@@ -124,24 +121,15 @@ class NetworkCmProxyControllerSpec extends Specification {
         .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
 
     void setup() {
-        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
-            DatastoreType.OPERATIONAL) >>
-            new NcmpDatastoreOperationalResourceRequestHandler(
-                mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
+        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
+            OPERATIONAL) >> getNcmpDatastoreRequestHandler(OPERATIONAL,new NcmpCachedResourceRequestHandler(mockNetworkCmProxyQueryService))
 
-        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
-            DatastoreType.PASSTHROUGH_OPERATIONAL) >>
-            new NcmpDatastorePassthroughOperationalResourceRequestHandler(
-                mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
+        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
+            PASSTHROUGH_OPERATIONAL) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_OPERATIONAL,new NcmpPassthroughResourceRequestHandler())
 
-        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
-            DatastoreType.PASSTHROUGH_RUNNING) >>
-            new NcmpDatastorePassthroughRunningResourceRequestHandler(
-                mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
+        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
+            PASSTHROUGH_RUNNING) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_RUNNING,new NcmpPassthroughResourceRequestHandler())
 
-        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceQueryHandler() >>
-            new NcmpDatastoreOperationalQueryHandler(mockNetworkCmProxyQueryService, spiedCpsTaskExecutor,
-                TIMEOUT_IN_MS, NOTIFICATION_ENABLED);
     }
 
     def 'Get Resource Data from pass-through operational.'() {
@@ -154,11 +142,8 @@ class NetworkCmProxyControllerSpec extends Specification {
                     .contentType(MediaType.APPLICATION_JSON)
             ).andReturn().response
         then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
-            1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
-                'parent/child',
-                '(a=1,b=2)',
-                NO_TOPIC,
-                NO_REQUEST_ID)
+            1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
+                'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID)
         and: 'response status is Ok'
             response.status == HttpStatus.OK.value()
     }
@@ -205,6 +190,48 @@ class NetworkCmProxyControllerSpec extends Specification {
             'invalid non-empty topic value in url' | 'passthrough-running'     | '&topic=1_5_*_#'
     }
 
+    def 'Get bulk resource data for #datastoreName from dmi service.'() {
+        given: 'bulk resource data url'
+            def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastoreName}" +
+                    "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
+        when: 'post data resource request is performed'
+            def response = mvc.perform(
+                    post(getUrl)
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(bulkRequestBody)
+            ).andReturn().response
+        then: 'response status is Ok'
+            response.status == HttpStatus.OK.value()
+            // TODO Need to be un-commented as it's failing into onap CICD pipeline
+            //  but passed into nordix and local build.
+            //and: 'the NCMP data service is called with getResourceDataForCmHandleBatch'
+            // 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'],
+            //        'parent/child',
+            //       '(a=1,b=2)',
+            //      'myTopic',
+            //     _)
+        and: 'async request id is generated'
+            assert response.contentAsString.contains("requestId")
+        where: 'the following data stores are used'
+            datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
+    }
+
+    def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() {
+        given: 'bulk resource data url'
+            def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" +
+                    "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
+        when: 'post data resource request is performed'
+            def response = mvc.perform(
+                    post(getUrl)
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(bulkRequestBody)
+            ).andReturn().response
+        then: 'response status code is 501 not implemented'
+            response.status == HttpStatus.NOT_IMPLEMENTED.value()
+        where: 'the following data store is un-supported'
+            datastoreName << [OPERATIONAL.datastoreName]
+    }
+
     def 'Query Resource Data from operational.'() {
         given: 'the query resource data url'
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" +
@@ -244,11 +271,8 @@ class NetworkCmProxyControllerSpec extends Specification {
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
                 "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
         and: 'ncmp service returns json object'
-            mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                resourceIdentifier,
-                '(a=1,b=2)',
-                NO_TOPIC,
-                NO_REQUEST_ID) >> '{valid-json}'
+            mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
+                resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) >> '{valid-json}'
         when: 'get data resource request is performed'
             def response = mvc.perform(
                 get(getUrl)
@@ -531,9 +555,7 @@ class NetworkCmProxyControllerSpec extends Specification {
                     .contentType(MediaType.APPLICATION_JSON)
             ).andReturn().response
         then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
-            1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle',
-                'parent/child',
-                descendantsOption)
+            1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption)
         and: 'response status is Ok'
             response.status == HttpStatus.OK.value()
         where: 'the following parameters are used'
@@ -626,5 +648,19 @@ class NetworkCmProxyControllerSpec extends Specification {
         return assertContainsAll(response, expectedContent)
     }
 
+    def getNcmpDatastoreRequestHandler(dataStoreType, ncmpDatastoreRequestHandler) {
+        if (ncmpDatastoreRequestHandler instanceof NcmpCachedResourceRequestHandler) {
+            NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = (NcmpCachedResourceRequestHandler) ncmpDatastoreRequestHandler
+            ncmpCachedResourceRequestHandler.dataStoreName = dataStoreType.datastoreName
+        } else {
+            NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = (NcmpPassthroughResourceRequestHandler) ncmpDatastoreRequestHandler
+            ncmpPassthroughResourceRequestHandler.dataStoreName = dataStoreType.datastoreName
+        }
+        ncmpDatastoreRequestHandler.networkCmProxyDataService = mockNetworkCmProxyDataService
+        ncmpDatastoreRequestHandler.cpsNcmpTaskExecutor = spiedCpsTaskExecutor
+        ncmpDatastoreRequestHandler.notificationFeatureEnabled = NOTIFICATION_ENABLED
+        ncmpDatastoreRequestHandler.timeOutInMilliSeconds = TIMEOUT_IN_MS
+        return ncmpDatastoreRequestHandler
+    }
 }
 
index 7c50498..15b3ee6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
 
 package org.onap.cps.ncmp.rest.controller.handlers
 
+import org.spockframework.spring.SpringBean
 import spock.lang.Specification
 
 class NcmpDatastoreRequestHandlerFactorySpec extends Specification {
 
-    def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(null, null, null)
+    @SpringBean
+    NcmpCachedResourceRequestHandler mockNcmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(null)
+
+    @SpringBean
+    NcmpPassthroughResourceRequestHandler mockNcmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler()
+
+    def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler)
 
     def 'Creating ncmp datastore request handlers.'() {
         when: 'a ncmp datastore request handler is created for #datastoreType'
-            def result = objectUnderTest.getNcmpDatastoreResourceRequestHandler(datastoreType)
+            def result = objectUnderTest.getNcmpResourceRequestHandler(datastoreType)
         then: 'the result is of the expected class'
             result.class == expectedClass
         where: 'the following type of datastore is used'
             datastoreType                         || expectedClass
-            DatastoreType.OPERATIONAL             || NcmpDatastoreOperationalResourceRequestHandler
-            DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpDatastorePassthroughOperationalResourceRequestHandler
-            DatastoreType.PASSTHROUGH_RUNNING     || NcmpDatastorePassthroughRunningResourceRequestHandler
+            DatastoreType.OPERATIONAL             || NcmpCachedResourceRequestHandler
+            DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpPassthroughResourceRequestHandler
+            DatastoreType.PASSTHROUGH_RUNNING     || NcmpPassthroughResourceRequestHandler
     }
 }
index 128eed3..03737bc 100644 (file)
 
 package org.onap.cps.ncmp.api;
 
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
-
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
+import org.onap.cps.ncmp.api.impl.operations.OperationEnum;
 import org.onap.cps.ncmp.api.inventory.CompositeState;
 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
@@ -51,50 +51,55 @@ public interface NetworkCmProxyDataService {
     DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration);
 
     /**
-     * Get resource data for data store pass-through operational
-     * using dmi.
+     * Get resource data for given data store using dmi.
      *
-     * @param cmHandleId cm handle identifier
-     * @param resourceIdentifier resource identifier
+     * @param dataStoreName       data store name
+     * @param cmHandleId          cm handle identifier
+     * @param resourceIdentifier  resource identifier
      * @param optionsParamInQuery options query
-     * @param topicParamInQuery topic name for (triggering) async responses
-     * @param requestId unique requestId for async request
+     * @param topicParamInQuery   topic name for (triggering) async responses
+     * @param requestId           unique requestId for async request
      * @return {@code Object} resource data
      */
-    Object getResourceDataOperationalForCmHandle(String cmHandleId,
-                                                 String resourceIdentifier,
-                                                 String optionsParamInQuery,
-                                                 String topicParamInQuery,
-                                                 String requestId);
+    Object getResourceDataForCmHandle(String dataStoreName,
+                                      String cmHandleId,
+                                      String resourceIdentifier,
+                                      String optionsParamInQuery,
+                                      String topicParamInQuery,
+                                      String requestId);
 
     /**
      * Get resource data for operational.
      *
+     * @param dataStoreName       data store name
      * @param cmHandleId cm handle identifier
      * @param resourceIdentifier resource identifier
      * @Link FetchDescendantsOption fetch descendants option
      * @return {@code Object} resource data
      */
-    Object getResourceDataOperational(String cmHandleId,
+    Object getResourceDataForCmHandle(String dataStoreName,
+                                      String cmHandleId,
                                       String resourceIdentifier,
                                       FetchDescendantsOption fetchDescendantsOption);
 
     /**
-     * Get resource data for data store pass-through running
-     * using dmi.
+     * Get resource data for given batch of cm handles using dmi.
      *
-     * @param cmHandleId cm handle identifier
-     * @param resourceIdentifier resource identifier
+     * @param dataStoreName       data store name
+     * @param cmHandleIds         cm handle identifiers
+     * @param resourceIdentifier  resource identifier
      * @param optionsParamInQuery options query
-     * @param topicParamInQuery topic name for (triggering) async responses
-     * @param requestId unique requestId for async request
+     * @param topicParamInQuery   topic name for (triggering) async responses
+     * @param requestId           unique requestId for async request
      * @return {@code Object} resource data
      */
-    Object getResourceDataPassThroughRunningForCmHandle(String cmHandleId,
-                                                        String resourceIdentifier,
-                                                        String optionsParamInQuery,
-                                                        String topicParamInQuery,
-                                                        String requestId);
+    Object getResourceDataForCmHandleBatch(String dataStoreName,
+                                       List<String> cmHandleIds,
+                                       String resourceIdentifier,
+                                       String optionsParamInQuery,
+                                       String topicParamInQuery,
+                                       String requestId);
+
 
     /**
      * Write resource data for data store pass-through running
index b3904bd..1b1997f 100755 (executable)
@@ -25,7 +25,6 @@
 package org.onap.cps.ncmp.api.impl;
 
 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
 import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCmHandleQueryParameters;
 
 import com.google.common.collect.Lists;
@@ -46,7 +45,7 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
 import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler;
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
-import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
+import org.onap.cps.ncmp.api.impl.operations.OperationEnum;
 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;
@@ -115,38 +114,41 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     @Override
-    public Object getResourceDataOperationalForCmHandle(final String cmHandleId,
-                                                        final String resourceIdentifier,
-                                                        final String optionsParamInQuery,
-                                                        final String topicParamInQuery,
-                                                        final String requestId) {
-        final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId,
+    public Object getResourceDataForCmHandle(final String dataStoreName,
+                                             final String cmHandleId,
+                                             final String resourceIdentifier,
+                                             final String optionsParamInQuery,
+                                             final String topicParamInQuery,
+                                             final String requestId) {
+        final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(dataStoreName, cmHandleId,
                 resourceIdentifier,
                 optionsParamInQuery,
-                DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL,
-                requestId, topicParamInQuery);
+                topicParamInQuery,
+                requestId);
         return responseEntity.getBody();
     }
 
     @Override
-    public Object getResourceDataOperational(final String cmHandleId,
+    public Object getResourceDataForCmHandle(final String dataStoreName,
+                                             final String cmHandleId,
                                              final String resourceIdentifier,
                                              final FetchDescendantsOption fetchDescendantsOption) {
-        return cpsDataService.getDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier,
+        return cpsDataService.getDataNodes(dataStoreName, cmHandleId, resourceIdentifier,
                 fetchDescendantsOption).iterator().next();
     }
 
     @Override
-    public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId,
-                                                               final String resourceIdentifier,
-                                                               final String optionsParamInQuery,
-                                                               final String topicParamInQuery,
-                                                               final String requestId) {
-        final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId,
+    public Object getResourceDataForCmHandleBatch(final String dataStoreName,
+                                                  final List<String> cmHandleIds,
+                                                  final String resourceIdentifier,
+                                                  final String optionsParamInQuery,
+                                                  final String topicParamInQuery,
+                                                  final String requestId) {
+        final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(dataStoreName, cmHandleIds,
                 resourceIdentifier,
                 optionsParamInQuery,
-                DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING,
-                requestId, topicParamInQuery);
+                topicParamInQuery,
+                requestId);
         return responseEntity.getBody();
     }
 
index d5b459b..9d08780 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@ package org.onap.cps.ncmp.api.impl.client;
 import lombok.AllArgsConstructor;
 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.DmiRequestBody;
+import org.onap.cps.ncmp.api.impl.operations.OperationEnum;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
@@ -43,14 +43,14 @@ public class DmiRestClient {
     /**
      * Sends POST operation to DMI with json body containing module references.
      * @param dmiResourceUrl dmi resource url
-     * @param jsonData json data body
+     * @param requestBodyAsJsonString json data body
      * @param operation the type of operation being executed (for error reporting only)
      * @return response entity of type String
      */
     public ResponseEntity<Object> postOperationWithJsonData(final String dmiResourceUrl,
-                                                            final String jsonData,
-                                                            final DmiRequestBody.OperationEnum operation) {
-        final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders(new HttpHeaders()));
+                                                            final String requestBodyAsJsonString,
+                                                            final OperationEnum operation) {
+        final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders()));
         try {
             return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class);
         } catch (final HttpStatusCodeException httpStatusCodeException) {
index ac2bd45..ff7afc9 100644 (file)
 
 package org.onap.cps.ncmp.api.impl.config.embeddedcache;
 
-import com.hazelcast.config.Config;
 import com.hazelcast.config.MapConfig;
-import com.hazelcast.config.NamedConfig;
 import com.hazelcast.config.QueueConfig;
-import com.hazelcast.core.Hazelcast;
-import com.hazelcast.core.HazelcastInstance;
 import com.hazelcast.map.IMap;
 import java.util.concurrent.BlockingQueue;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.cache.HazelcastCacheConfig;
 import org.onap.cps.spi.model.DataNode;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -35,13 +33,14 @@ import org.springframework.context.annotation.Configuration;
 /**
  * Core infrastructure of the hazelcast distributed caches for Module Sync and Data Sync use cases.
  */
+@Slf4j
 @Configuration
-public class SynchronizationCacheConfig {
+public class SynchronizationCacheConfig extends HazelcastCacheConfig {
 
     public static final int MODULE_SYNC_STARTED_TTL_SECS = 600;
     public static final int DATA_SYNC_SEMAPHORE_TTL_SECS = 1800;
 
-    private static final QueueConfig commonQueueConfig = createQueueConfig();
+    private static final QueueConfig commonQueueConfig = createQueueConfig("defaultQueueConfig");
     private static final MapConfig moduleSyncStartedConfig = createMapConfig("moduleSyncStartedConfig");
     private static final MapConfig dataSyncSemaphoresConfig = createMapConfig("dataSyncSemaphoresConfig");
 
@@ -52,7 +51,8 @@ public class SynchronizationCacheConfig {
      */
     @Bean
     public BlockingQueue<DataNode> moduleSyncWorkQueue() {
-        return createHazelcastInstance("moduleSyncWorkQueue", commonQueueConfig)
+        return createHazelcastInstance("moduleSyncWorkQueue", commonQueueConfig,
+            "synchronization-caches")
             .getQueue("moduleSyncWorkQueue");
     }
 
@@ -63,7 +63,8 @@ public class SynchronizationCacheConfig {
      */
     @Bean
     public IMap<String, Object> moduleSyncStartedOnCmHandles() {
-        return createHazelcastInstance("moduleSyncStartedOnCmHandles", moduleSyncStartedConfig)
+        return createHazelcastInstance("moduleSyncStartedOnCmHandles", moduleSyncStartedConfig,
+            "synchronization-caches")
             .getMap("moduleSyncStartedOnCmHandles");
     }
 
@@ -74,39 +75,8 @@ public class SynchronizationCacheConfig {
      */
     @Bean
     public IMap<String, Boolean> dataSyncSemaphores() {
-        return createHazelcastInstance("dataSyncSemaphores", dataSyncSemaphoresConfig)
+        return createHazelcastInstance("dataSyncSemaphores", dataSyncSemaphoresConfig,
+            "synchronization-caches")
             .getMap("dataSyncSemaphores");
     }
-
-    private HazelcastInstance createHazelcastInstance(
-        final String hazelcastInstanceName, final NamedConfig namedConfig) {
-        return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig));
-    }
-
-    private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) {
-        final Config config = new Config(instanceName);
-        if (namedConfig instanceof MapConfig) {
-            config.addMapConfig((MapConfig) namedConfig);
-        }
-        if (namedConfig instanceof QueueConfig) {
-            config.addQueueConfig((QueueConfig) namedConfig);
-        }
-        config.setClusterName("synchronization-caches");
-        return config;
-    }
-
-    private static QueueConfig createQueueConfig() {
-        final QueueConfig commonQueueConfig = new QueueConfig("defaultQueueConfig");
-        commonQueueConfig.setBackupCount(3);
-        commonQueueConfig.setAsyncBackupCount(3);
-        return commonQueueConfig;
-    }
-
-    private static MapConfig createMapConfig(final String configName) {
-        final MapConfig mapConfig = new MapConfig(configName);
-        mapConfig.setBackupCount(3);
-        mapConfig.setAsyncBackupCount(3);
-        return mapConfig;
-    }
-
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfig.java
new file mode 100644 (file)
index 0000000..d2c3dc2
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * ============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.event.avc;
+
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.map.IMap;
+import java.util.Set;
+import org.onap.cps.cache.HazelcastCacheConfig;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Core infrastructure of the hazelcast distributed cache for subscription forward config use cases.
+ */
+@Configuration
+public class ForwardedSubscriptionEventCacheConfig extends HazelcastCacheConfig {
+
+    private static final MapConfig forwardedSubscriptionEventCacheMapConfig =
+        createMapConfig("forwardedSubscriptionEventCacheMapConfig");
+
+    /**
+     * Distributed instance of forwarded subscription information cache that contains subscription event
+     * id by dmi names as properties.
+     *
+     * @return configured map of subscription event ids as keys to sets of dmi names for values
+     */
+    @Bean
+    public IMap<String, Set<String>> forwardedSubscriptionEventCache() {
+        return createHazelcastInstance("hazelCastInstanceSubscriptionEvents",
+            forwardedSubscriptionEventCacheMapConfig, "cps-ncmp-service-caches")
+            .getMap("forwardedSubscriptionEventCache");
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ResponseTimeoutTask.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ResponseTimeoutTask.java
new file mode 100644 (file)
index 0000000..e7edecf
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * ============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.event.avc;
+
+import com.hazelcast.map.IMap;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RequiredArgsConstructor
+public class ResponseTimeoutTask implements Runnable {
+
+    private final IMap<String, Set<String>> forwardedSubscriptionEventCache;
+    private final String subscriptionEventId;
+
+    @Override
+    public void run() {
+        if (forwardedSubscriptionEventCache.containsKey(subscriptionEventId)) {
+            final Set<String> dmiNames = forwardedSubscriptionEventCache.get(subscriptionEventId);
+            if (dmiNames.isEmpty()) {
+                //TODO full outcome response here
+                log.info("placeholder to create full outcome response for subscriptionEventId: {}.",
+                    subscriptionEventId);
+            } else {
+                //TODO partial outcome response here
+                log.info("placeholder to create partial outcome response for subscriptionEventId: {}.",
+                    subscriptionEventId);
+            }
+            forwardedSubscriptionEventCache.remove(subscriptionEventId);
+        }
+    }
+}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumer.java
new file mode 100644 (file)
index 0000000..b332ad1
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * ============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.event.avc;
+
+import com.hazelcast.map.IMap;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.models.SubscriptionEventResponse;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class SubscriptionEventResponseConsumer {
+
+    private final IMap<String, Set<String>> forwardedSubscriptionEventCache;
+
+    @Value("${app.ncmp.avc.subscription-outcome-topic}")
+    private String subscriptionOutcomeEventTopic;
+
+    @Value("${notification.enabled:true}")
+    private boolean notificationFeatureEnabled;
+
+    @Value("${ncmp.model-loader.subscription:false}")
+    private boolean subscriptionModelLoaderEnabled;
+
+    /**
+     * Consume subscription response event.
+     *
+     * @param subscriptionEventResponse the event to be consumed
+     */
+    @KafkaListener(topics = "${app.ncmp.avc.subscription-response-topic}",
+        properties = {"spring.json.value.default.type=org.onap.cps.ncmp.api.models.SubscriptionEventResponse"})
+    public void consumeSubscriptionEventResponse(final SubscriptionEventResponse subscriptionEventResponse) {
+        log.info("subscription event response of clientId: {} is received.", subscriptionEventResponse.getClientId());
+        final String subscriptionEventId = subscriptionEventResponse.getClientId()
+            + subscriptionEventResponse.getSubscriptionName();
+        final boolean createOutcomeResponse;
+        if (forwardedSubscriptionEventCache.containsKey(subscriptionEventId)) {
+            forwardedSubscriptionEventCache.get(subscriptionEventId).remove(subscriptionEventResponse.getDmiName());
+            createOutcomeResponse = forwardedSubscriptionEventCache.get(subscriptionEventId).isEmpty();
+            if (createOutcomeResponse) {
+                forwardedSubscriptionEventCache.remove(subscriptionEventId);
+            }
+        } else {
+            createOutcomeResponse = true;
+        }
+        if (subscriptionModelLoaderEnabled) {
+            updateSubscriptionEvent(subscriptionEventResponse);
+        }
+        if (createOutcomeResponse && notificationFeatureEnabled) {
+            log.info("placeholder to create full outcome response for subscriptionEventId: {}.", subscriptionEventId);
+            //TODO Create outcome response
+        }
+    }
+
+    private void updateSubscriptionEvent(final SubscriptionEventResponse subscriptionEventResponse) {
+        log.info("placeholder to update persisted subscription for subscriptionEventId: {}.",
+            subscriptionEventResponse.getClientId() + subscriptionEventResponse.getSubscriptionName());
+    }
+}
\ No newline at end of file
index c3624b8..4afa051 100644 (file)
 
 package org.onap.cps.ncmp.api.impl.events.avcsubscription;
 
+import com.hazelcast.map.IMap;
 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.Objects;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.impl.event.avc.ResponseTimeoutTask;
 import org.onap.cps.ncmp.api.impl.events.EventsPublisher;
-import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService;
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence;
 import org.onap.cps.ncmp.event.model.SubscriptionEvent;
 import org.onap.cps.spi.exceptions.OperationNotYetSupportedException;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 
@@ -45,9 +52,15 @@ public class SubscriptionEventForwarder {
 
     private final InventoryPersistence inventoryPersistence;
     private final EventsPublisher<SubscriptionEvent> eventsPublisher;
+    private final IMap<String, Set<String>> forwardedSubscriptionEventCache;
+
+    private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
 
     private static final String DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX = "ncmp-dmi-cm-avc-subscription-";
 
+    @Value("${ncmp.timers.subscription-forwarding.dmi-response-timeout-ms:30000}")
+    private int dmiResponseTimeoutInMs;
+
     /**
      * Forward subscription event.
      *
@@ -56,38 +69,41 @@ public class SubscriptionEventForwarder {
     public void forwardCreateSubscriptionEvent(final SubscriptionEvent subscriptionEvent) {
         final List<Object> cmHandleTargets = subscriptionEvent.getEvent().getPredicates().getTargets();
         if (cmHandleTargets == null || cmHandleTargets.isEmpty()
-            || cmHandleTargets.stream().anyMatch(id -> ((String) id).contains("*"))) {
+                || cmHandleTargets.stream().anyMatch(id -> ((String) id).contains("*"))) {
             throw new OperationNotYetSupportedException(
-                "CMHandle targets are required. \"Wildcard\" operations are not yet supported");
+                    "CMHandle targets are required. \"Wildcard\" operations are not yet supported");
         }
         final List<String> cmHandleTargetsAsStrings = cmHandleTargets.stream().map(
-            Objects::toString).collect(Collectors.toList());
+                Objects::toString).collect(Collectors.toList());
         final Collection<YangModelCmHandle> yangModelCmHandles =
-            inventoryPersistence.getYangModelCmHandles(cmHandleTargetsAsStrings);
-        final Map<String, Map<String, Map<String, String>>> dmiNameCmHandleMap =
-            organizeByDmiName(yangModelCmHandles);
+                inventoryPersistence.getYangModelCmHandles(cmHandleTargetsAsStrings);
+
+        final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName
+                = DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles);
+
+        final Set<String> dmisToRespond = new HashSet<>(dmiPropertiesPerCmHandleIdPerServiceName.keySet());
+        startResponseTimeout(subscriptionEvent, dmisToRespond);
+        forwardEventToDmis(dmiPropertiesPerCmHandleIdPerServiceName, subscriptionEvent);
+    }
+
+    private void forwardEventToDmis(final Map<String, Map<String, Map<String, String>>> dmiNameCmHandleMap,
+                                    final SubscriptionEvent subscriptionEvent) {
         dmiNameCmHandleMap.forEach((dmiName, cmHandlePropertiesMap) -> {
             subscriptionEvent.getEvent().getPredicates().setTargets(Collections.singletonList(cmHandlePropertiesMap));
             final String eventKey = createEventKey(subscriptionEvent, dmiName);
-            eventsPublisher.publishEvent(DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX + dmiName, eventKey, subscriptionEvent);
+            eventsPublisher.publishEvent(
+                DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX + dmiName, eventKey, subscriptionEvent);
         });
     }
 
-    private Map<String, Map<String, Map<String, String>>> organizeByDmiName(
-        final Collection<YangModelCmHandle> yangModelCmHandles) {
-        final Map<String, Map<String, Map<String, String>>> dmiNameCmHandlePropertiesMap = new HashMap<>();
-        yangModelCmHandles.forEach(cmHandle -> {
-            final String dmiName = cmHandle.resolveDmiServiceName(RequiredDmiService.DATA);
-            if (!dmiNameCmHandlePropertiesMap.containsKey(dmiName)) {
-                final Map<String, Map<String, String>> cmHandleDmiPropertiesMap = new HashMap<>();
-                cmHandleDmiPropertiesMap.put(cmHandle.getId(), dmiPropertiesAsMap(cmHandle));
-                dmiNameCmHandlePropertiesMap.put(cmHandle.getDmiDataServiceName(), cmHandleDmiPropertiesMap);
-            } else {
-                dmiNameCmHandlePropertiesMap.get(cmHandle.getDmiDataServiceName())
-                    .put(cmHandle.getId(), dmiPropertiesAsMap(cmHandle));
-            }
-        });
-        return dmiNameCmHandlePropertiesMap;
+    private void startResponseTimeout(final SubscriptionEvent subscriptionEvent, final Set<String> dmisToRespond) {
+        final String subscriptionEventId = subscriptionEvent.getEvent().getSubscription().getClientID()
+            + subscriptionEvent.getEvent().getSubscription().getName();
+
+        forwardedSubscriptionEventCache.put(subscriptionEventId, dmisToRespond);
+        final ResponseTimeoutTask responseTimeoutTask =
+            new ResponseTimeoutTask(forwardedSubscriptionEventCache, subscriptionEventId);
+        executorService.schedule(responseTimeoutTask, dmiResponseTimeoutInMs, TimeUnit.MILLISECONDS);
     }
 
     private String createEventKey(final SubscriptionEvent subscriptionEvent, final String dmiName) {
@@ -98,9 +114,4 @@ public class SubscriptionEventForwarder {
             + dmiName;
     }
 
-    public Map<String, String> dmiPropertiesAsMap(final YangModelCmHandle yangModelCmHandle) {
-        return yangModelCmHandle.getDmiProperties().stream().collect(
-            Collectors.toMap(YangModelCmHandle.Property::getName, YangModelCmHandle.Property::getValue));
-    }
-
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/executor/TaskExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/executor/TaskExecutor.java
new file mode 100644 (file)
index 0000000..192062f
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *  ============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.executor;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TaskExecutor {
+
+    /**
+     * Execute task asynchronously.
+     *
+     * @param taskSupplier    functional method is get() task need to executed asynchronously
+     * @param timeOutInMillis the timeout value in milliseconds
+     */
+    public static CompletableFuture<Object> executeTask(final Supplier<Object> taskSupplier,
+                                                        final long timeOutInMillis) {
+        return CompletableFuture.supplyAsync(taskSupplier::get)
+                .orTimeout(timeOutInMillis, MILLISECONDS);
+    }
+}
+
+
+
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DataStoreEnum.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DataStoreEnum.java
new file mode 100644 (file)
index 0000000..24edc73
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  ============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.operations;
+
+import lombok.Getter;
+
+@Getter
+public enum DataStoreEnum {
+    PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"),
+    PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running");
+    private final String value;
+
+    DataStoreEnum(final String value) {
+        this.value = value;
+    }
+}
index 83faa00..d648352 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 
 package org.onap.cps.ncmp.api.impl.operations;
 
-import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING;
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ;
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING;
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 import lombok.extern.slf4j.Slf4j;
 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.executor.TaskExecutor;
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer;
 import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.ncmp.api.inventory.CmHandleState;
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
 
@@ -44,15 +49,14 @@ import org.springframework.stereotype.Component;
 @Slf4j
 public class DmiDataOperations extends DmiOperations {
 
-    /**
-     * Constructor for {@code DmiOperations}. This method also manipulates url properties.
-     *
-     * @param dmiRestClient {@code DmiRestClient}
-     */
+    private static final long DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS = 30000L;
+    private static final String NO_CM_HANDLE_ID = "";
+
     public DmiDataOperations(final InventoryPersistence inventoryPersistence,
                              final JsonObjectMapper jsonObjectMapper,
                              final NcmpConfiguration.DmiProperties dmiProperties,
-                             final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
+                             final DmiRestClient dmiRestClient,
+                             final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
         super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder);
     }
 
@@ -60,48 +64,78 @@ public class DmiDataOperations extends DmiOperations {
      * This method fetches the resource data from operational data store for given cm handle
      * identifier on given resource using dmi client.
      *
-     * @param cmHandleId    network resource identifier
-     * @param resourceId  resource identifier
+     * @param dataStoreName       name of data store
+     * @param cmHandleId          network resource identifier
+     * @param resourceId          resource identifier
      * @param optionsParamInQuery options query
-     * @param dataStore           data store enum
-     * @param requestId           requestId for async responses
      * @param topicParamInQuery   topic name for (triggering) async responses
+     * @param requestId           requestId for async responses
      * @return {@code ResponseEntity} response entity
      */
-    public ResponseEntity<Object> getResourceDataFromDmi(final String cmHandleId,
+    public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName,
+                                                         final String cmHandleId,
                                                          final String resourceId,
                                                          final String optionsParamInQuery,
-                                                         final DataStoreEnum dataStore,
-                                                         final String requestId,
-                                                         final String topicParamInQuery) {
+                                                         final String topicParamInQuery,
+                                                         final String requestId) {
         final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
-        final String jsonBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle);
-        final String dmiResourceDataUrl = getDmiRequestUrl(cmHandleId, resourceId, optionsParamInQuery, dataStore,
-                topicParamInQuery, yangModelCmHandle);
         final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
-        isCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, READ);
+        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
+        final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null,
+                yangModelCmHandle);
+        final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, resourceId, optionsParamInQuery,
+                topicParamInQuery, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
+        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ);
+    }
+
+    /**
+     * This method fetches the resource data by data store for given list of cm handles using dmi client.
+     *
+     * @param dataStoreName           data store name
+     * @param cmHandleIds         list of cm handles
+     * @param resourceId          resource identifier
+     * @param optionsParamInQuery options query
+     * @param topicParamInQuery   topic name for (triggering) async responses
+     * @param requestId           requestId for async responses
+     * @return {@code ResponseEntity} response entity
+     */
+    public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName,
+                                                         final List<String> cmHandleIds,
+                                                         final String resourceId,
+                                                         final String optionsParamInQuery,
+                                                         final String topicParamInQuery,
+                                                         final String requestId) {
+        final Collection<YangModelCmHandle> yangModelCmHandles
+                = inventoryPersistence.getYangModelCmHandles(cmHandleIds);
+        final Map<String, Map<String, Map<String, String>>> dmiServiceNameCmHandlePropertiesMap =
+                DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles);
+
+        buildBulkResourceDataRequestAndSend(dataStoreName, resourceId, optionsParamInQuery,
+                topicParamInQuery, requestId, dmiServiceNameCmHandlePropertiesMap);
+        return new ResponseEntity<>(HttpStatus.ACCEPTED);
     }
 
     /**
      * 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 dataStore  data store enum
      * @param requestId  requestId for async responses
      * @return {@code ResponseEntity} response entity
      */
-    public ResponseEntity<Object> getResourceDataFromDmi(final String cmHandleId,
-                                                         final DataStoreEnum dataStore,
+    public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName,
+                                                         final String cmHandleId,
                                                          final String requestId) {
         final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
-        final String jsonBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle);
-        final String dmiResourceDataUrl = getDmiRequestUrl(cmHandleId, "/", null, dataStore,
-                null, yangModelCmHandle);
+        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();
-        isCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, READ);
+        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
+        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody,
+                READ);
     }
 
     /**
@@ -121,12 +155,14 @@ public class DmiDataOperations extends DmiOperations {
                                                                              final String requestData,
                                                                              final String dataType) {
         final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
-        final String jsonBody = getDmiRequestBody(operation, null, requestData, dataType, yangModelCmHandle);
-        final String dmiUrl = getDmiRequestUrl(cmHandleId, resourceId, null, PASSTHROUGH_RUNNING,
-                null, yangModelCmHandle);
+        final String jsonRequestBody = getDmiRequestBody(operation, null, requestData, dataType,
+                yangModelCmHandle);
+        final String dmiUrl = getDmiRequestUrl(PASSTHROUGH_RUNNING.getValue(), cmHandleId, resourceId,
+                null, null,
+                yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
         final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
-        isCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody, operation);
+        validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
+        return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operation);
     }
 
     private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
@@ -145,24 +181,80 @@ public class DmiDataOperations extends DmiOperations {
         return jsonObjectMapper.asJsonString(dmiRequestBody);
     }
 
-    private String getDmiRequestUrl(final String cmHandleId,
-                                      final String resourceId,
-                                      final String optionsParamInQuery,
-                                      final DataStoreEnum dataStore,
-                                      final String topicParamInQuery,
-                                      final YangModelCmHandle yangModelCmHandle) {
+    private String getDmiBulkRequestBody(final OperationEnum operation,
+                                         final String requestId,
+                                         final String requestData) {
+        final DmiRequestBody dmiBulkRequestBody = DmiRequestBody.builder()
+                .operation(operation)
+                .requestId(requestId)
+                .data(requestData)
+                .build();
+        return jsonObjectMapper.asJsonString(dmiBulkRequestBody);
+    }
+
+    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(
-                        yangModelCmHandle, cmHandleId, dataStore));
+                        topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName,
+                        cmHandleId));
     }
 
-    private void isCmHandleStateReady(final YangModelCmHandle yangModelCmHandle, final CmHandleState cmHandleState) {
+    private String getDmiServiceBulkRequestUrl(final String dataStoreName,
+                                               final String resourceId,
+                                               final String optionsParamInQuery,
+                                               final String topicParamInQuery,
+                                               final String dmiServiceName) {
+        return dmiServiceUrlBuilder.getBulkRequestUrl(
+                dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery,
+                        topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName,
+                        NO_CM_HANDLE_ID));
+    }
+
+    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());
+                    + "cm handle state is "
+                    + yangModelCmHandle.getCompositeState().getCmHandleState());
         }
     }
 
+    private void buildBulkResourceDataRequestAndSend(final String dataStoreName,
+                                                     final String resourceId,
+                                                     final String optionsParamInQuery,
+                                                     final String topicParamInQuery,
+                                                     final String requestId,
+                                                     final Map<String, Map<String, Map<String, String>>>
+                                                             dmiServiceNameCmHandlePropertiesMap) {
+        dmiServiceNameCmHandlePropertiesMap.entrySet().parallelStream().forEach(
+                dmiServiceNameCmHandlePropertiesEntry -> {
+                    final String dmiBulkResourceDataUrl = getDmiServiceBulkRequestUrl(dataStoreName, resourceId,
+                            optionsParamInQuery, topicParamInQuery, dmiServiceNameCmHandlePropertiesEntry.getKey());
+                    final String jsonRequestBodyAsJsonString =
+                            jsonObjectMapper.asJsonString(dmiServiceNameCmHandlePropertiesEntry.getValue());
+                    final String jsonRequestBody
+                            = getDmiBulkRequestBody(READ, requestId, jsonRequestBodyAsJsonString);
+                    sendDmiResourceDataRequestToDmiService(dmiBulkResourceDataUrl, jsonRequestBody);
+                });
+    }
+
+    private void sendDmiResourceDataRequestToDmiService(final String dmiBulkResourceDataUrl,
+                                                        final String dmiResourceDataRequestAsJsonString) {
+        TaskExecutor.executeTask(() ->
+                                dmiRestClient.postOperationWithJsonData(dmiBulkResourceDataUrl,
+                                        dmiResourceDataRequestAsJsonString, READ),
+                        DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS)
+                .whenCompleteAsync(this::handleTaskCompletion);
+    }
+
+    private void handleTaskCompletion(final Object response, final Throwable throwable) {
+        // TODO Need to publish an error response to client given topic.
+        //  Code should be implemented into https://jira.onap.org/browse/CPS-1558 (
+        //  NCMP : Handle non responding DMI-Plugin)
+    }
 }
index d8d0304..392e9c1 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -98,17 +98,18 @@ public class DmiModelOperations extends DmiOperations {
      * Get resources from DMI for modules.
      *
      * @param dmiServiceName dmi service name
-     * @param jsonData module names and revisions as JSON
+     * @param jsonRequestBody module names and revisions as JSON
      * @param cmHandle cmHandle
      * @param resourceName name of the resource(s)
      * @return {@code ResponseEntity} response entity
      */
     private ResponseEntity<Object> getResourceFromDmiWithJsonData(final String dmiServiceName,
-                                                                  final String jsonData,
+                                                                  final String jsonRequestBody,
                                                                   final String cmHandle,
                                                                   final String resourceName) {
         final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonData, DmiRequestBody.OperationEnum.READ);
+        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody,
+                OperationEnum.READ);
     }
 
     private static String getRequestBodyToFetchYangResources(final Collection<ModuleReference> newModuleReferences,
index e26ffef..7e9079e 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,6 @@
 
 package org.onap.cps.ncmp.api.impl.operations;
 
-import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
@@ -34,17 +33,6 @@ import org.springframework.stereotype.Service;
 @Service
 public class DmiOperations {
 
-    @Getter
-    public enum DataStoreEnum {
-        PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"),
-        PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running");
-        private final String value;
-
-        DataStoreEnum(final String value) {
-            this.value = value;
-        }
-    }
-
     protected final InventoryPersistence inventoryPersistence;
     protected final JsonObjectMapper jsonObjectMapper;
     protected final NcmpConfiguration.DmiProperties dmiProperties;
@@ -52,7 +40,7 @@ public class DmiOperations {
     protected final DmiServiceUrlBuilder dmiServiceUrlBuilder;
 
     String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) {
-        return dmiServiceUrlBuilder.getCmHandleUrl()
+        return dmiServiceUrlBuilder.getResourceDataBasePathUriBuilder()
                 .pathSegment("{resourceName}")
                 .buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString();
     }
index c84e4cb..3aa6366 100644 (file)
@@ -22,7 +22,6 @@ package org.onap.cps.ncmp.api.impl.operations;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonValue;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -34,24 +33,6 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 @Getter
 @Builder
 public class DmiRequestBody {
-    public enum OperationEnum {
-        READ("read"),
-        CREATE("create"),
-        UPDATE("update"),
-        PATCH("patch"),
-        DELETE("delete");
-        private final String value;
-
-        OperationEnum(final String value) {
-            this.value = value;
-        }
-
-        @Override
-        @JsonValue
-        public String toString() {
-            return String.valueOf(value);
-        }
-    }
 
     private OperationEnum operation;
     private String dataType;
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java
new file mode 100644 (file)
index 0000000..796cef2
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  ============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.operations;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public enum OperationEnum {
+
+    READ("read"),
+    CREATE("create"),
+    UPDATE("update"),
+    PATCH("patch"),
+    DELETE("delete");
+    private final String value;
+
+    OperationEnum(final String value) {
+        this.value = value;
+    }
+
+    @Override
+    @JsonValue
+    public String toString() {
+        return String.valueOf(value);
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java
new file mode 100644 (file)
index 0000000..26e9486
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  ============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.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+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;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DmiServiceNameOrganizer {
+
+    /**
+     * organizes a map with dmi service name as key for cm handle with its properties.
+     *
+     * @param yangModelCmHandles list of cm handle model
+     */
+    public static Map<String, Map<String, Map<String, String>>> getDmiPropertiesPerCmHandleIdPerServiceName(
+            final Collection<YangModelCmHandle> yangModelCmHandles) {
+        final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName
+                = new HashMap<>();
+        yangModelCmHandles.forEach(yangModelCmHandle -> {
+            final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA);
+            if (!dmiPropertiesPerCmHandleIdPerServiceName.containsKey(dmiServiceName)) {
+                final Map<String, Map<String, String>> cmHandleDmiPropertiesMap = new HashMap<>();
+                cmHandleDmiPropertiesMap.put(yangModelCmHandle.getId(),
+                        dmiPropertiesAsMap(yangModelCmHandle.getDmiProperties()));
+                dmiPropertiesPerCmHandleIdPerServiceName.put(dmiServiceName, cmHandleDmiPropertiesMap);
+            } else {
+                dmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName)
+                        .put(yangModelCmHandle.getId(), dmiPropertiesAsMap(yangModelCmHandle.getDmiProperties()));
+            }
+        });
+        return dmiPropertiesPerCmHandleIdPerServiceName;
+    }
+
+    private static Map<String, String> dmiPropertiesAsMap(final List<YangModelCmHandle.Property> dmiProperties) {
+        return dmiProperties.stream().collect(
+                Collectors.toMap(YangModelCmHandle.Property::getName, YangModelCmHandle.Property::getValue));
+    }
+}
index 5f4a654..bba8f48 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
 
 package org.onap.cps.ncmp.api.impl.utils;
 
-import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA;
-
 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.ncmp.api.impl.operations.DmiOperations;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.spi.utils.CpsValidator;
 import org.springframework.stereotype.Component;
 import org.springframework.util.LinkedMultiValueMap;
@@ -52,13 +48,21 @@ public class DmiServiceUrlBuilder {
      */
     public String getDmiDatastoreUrl(final MultiValueMap<String, String> queryParams,
                                      final Map<String, Object> uriVariables) {
-        final UriComponentsBuilder uriComponentsBuilder = getCmHandleUrl()
-                .pathSegment("data")
-                .pathSegment("ds")
-                .pathSegment("{dataStore}")
-                .queryParams(queryParams)
-                .uriVariables(uriVariables);
-        return uriComponentsBuilder.buildAndExpand().toUriString();
+        return getUriComponentsBuilder(getResourceDataBasePathUriBuilder(), queryParams, uriVariables)
+                .buildAndExpand().toUriString();
+    }
+
+    /**
+     * This method creates the dmi service url for bulk request.
+     *
+     * @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 getBulkRequestUrl(final MultiValueMap<String, String> queryParams,
+                                    final Map<String, Object> uriVariables) {
+        return getUriComponentsBuilder(getBulkResourceDataBasePathUriBuilder(), queryParams, uriVariables)
+                .buildAndExpand().toUriString();
     }
 
     /**
@@ -66,7 +70,7 @@ public class DmiServiceUrlBuilder {
      *
      * @return {@code UriComponentsBuilder} dmi service url builder object
      */
-    public UriComponentsBuilder getCmHandleUrl() {
+    public UriComponentsBuilder getResourceDataBasePathUriBuilder() {
         return UriComponentsBuilder.newInstance()
                 .path("{dmiServiceName}")
                 .pathSegment("{dmiBasePath}")
@@ -75,24 +79,37 @@ public class DmiServiceUrlBuilder {
                 .pathSegment("{cmHandleId}");
     }
 
+    /**
+     * This method creates the dmi service url builder object with path variables for batch of cm handles.
+     *
+     * @return {@code UriComponentsBuilder} dmi service url builder object
+     */
+    public UriComponentsBuilder getBulkResourceDataBasePathUriBuilder() {
+        return UriComponentsBuilder.newInstance()
+                .path("{dmiServiceName}")
+                .pathSegment("{dmiBasePath}")
+                .pathSegment("v1")
+                .pathSegment("batch");
+    }
+
     /**
      * This method populates uri variables.
      *
-     * @param yangModelCmHandle get dmi service name
+     * @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 YangModelCmHandle yangModelCmHandle,
-                                                    final String cmHandleId,
-                                                    final DmiOperations.DataStoreEnum dataStore) {
+    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",
-                yangModelCmHandle.resolveDmiServiceName(DATA));
+        uriVariables.put("dmiServiceName", dmiServiceName);
         uriVariables.put("dmiBasePath", dmiBasePath);
         uriVariables.put("cmHandleId", cmHandleId);
-        uriVariables.put("dataStore", dataStore.getValue());
+        uriVariables.put("dataStore", dataStoreName);
         return uriVariables;
     }
 
@@ -124,4 +141,15 @@ public class DmiServiceUrlBuilder {
             }
         };
     }
+
+    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);
+    }
 }
index 537f501..b9cecfb 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +21,8 @@
 
 package org.onap.cps.ncmp.api.inventory.sync;
 
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL;
+
 import com.fasterxml.jackson.databind.JsonNode;
 import java.time.Duration;
 import java.time.OffsetDateTime;
@@ -37,7 +39,6 @@ import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
-import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
 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.CmHandleQueries;
@@ -167,7 +168,8 @@ public class SyncUtils {
      */
     public String getResourceData(final String cmHandleId) {
         final ResponseEntity<Object> resourceDataResponseEntity = dmiDataOperations.getResourceDataFromDmi(
-                cmHandleId, DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL,
+                PASSTHROUGH_OPERATIONAL.getValue(),
+                cmHandleId,
                 UUID.randomUUID().toString());
         if (resourceDataResponseEntity.getStatusCode().is2xxSuccessful()) {
             return getFirstResource(resourceDataResponseEntity.getBody());
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java
new file mode 100644 (file)
index 0000000..95e773c
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  ============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.models;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionStatus;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Setter
+public class SubscriptionEventResponse {
+    private String clientId;
+    private String subscriptionName;
+    private String dmiName;
+    private Map<String, SubscriptionStatus> cmHandleIdToStatus;
+}
index 231ba75..659779a 100644 (file)
@@ -31,6 +31,7 @@ import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsModuleService;
 import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.model.Dataspace;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.context.event.ApplicationReadyEvent;
@@ -51,6 +52,12 @@ public class SubscriptionModelLoader implements ModelLoader {
     private static final String SUBSCRIPTION_SCHEMASET_NAME = "subscriptions";
     private static final String SUBSCRIPTION_REGISTRY_DATANODE_NAME = "subscription-registry";
 
+    @Value("${ncmp.model-loader.maximum-attempt-count:20}")
+    private int maximumAttemptCount;
+
+    @Value("${ncmp.timers.model-loader.retry-time-ms:1000}")
+    private long retryTimeMs;
+
     @Value("${ncmp.model-loader.subscription:false}")
     private boolean subscriptionModelLoaderEnabled;
 
@@ -63,6 +70,7 @@ public class SubscriptionModelLoader implements ModelLoader {
     public void onApplicationEvent(final ApplicationReadyEvent applicationReadyEvent) {
         try {
             if (subscriptionModelLoaderEnabled) {
+                checkNcmpDataspaceExists();
                 onboardSubscriptionModel(createYangResourceToContentMap());
             } else {
                 log.info("Subscription Model Loader is disabled");
@@ -73,6 +81,29 @@ public class SubscriptionModelLoader implements ModelLoader {
         }
     }
 
+    private void checkNcmpDataspaceExists() {
+        boolean ncmpDataspaceExists = false;
+        int attemptCount = 0;
+        while (!ncmpDataspaceExists) {
+            final Dataspace ncmpDataspace = cpsAdminService.getDataspace(SUBSCRIPTION_DATASPACE_NAME);
+            if (ncmpDataspace != null) {
+                ncmpDataspaceExists = true;
+            }
+            if (attemptCount < maximumAttemptCount) {
+                try {
+                    Thread.sleep(attemptCount * retryTimeMs);
+                    attemptCount++;
+                    log.info("Retrieving NCMP dataspace... {} attempt(s) ", attemptCount);
+                } catch (final InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            } else {
+                throw new NcmpStartUpException("Retrieval of NCMP dataspace fails",
+                    "NCMP dataspace does not exist");
+            }
+        }
+    }
+
     /**
      * Method to onboard subscription model for NCMP.
      */
@@ -108,7 +139,7 @@ public class SubscriptionModelLoader implements ModelLoader {
      */
     @Override
     public boolean createAnchor(final String dataspaceName, final String schemaSetName,
-                             final String anchorName) {
+                                final String anchorName) {
         try {
             cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName);
         } catch (final AlreadyDefinedException exception) {
index 8ae1be6..e332a28 100644 (file)
@@ -4,7 +4,7 @@ module subscription {
 
     prefix subs;
 
-    revision "2023-21-03" {
+    revision "2023-03-21" {
         description
             "NCMP subscription model";
     }
index 871af84..5b49e04 100644 (file)
@@ -52,10 +52,10 @@ import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
 import spock.lang.Specification
 
-import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
 
 class NetworkCmProxyDataServiceImplSpec extends Specification {
 
@@ -94,61 +94,64 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def 'Write resource data for pass-through running from DMI using POST.'() {
         given: 'cpsDataService returns valid datanode'
-            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
-                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+            mockDataNode()
         when: 'write resource data is called'
             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
                 'testResourceId', CREATE,
                 '{some-json}', 'application/json')
         then: 'DMI called with correct data'
             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
-                CREATE, '{some-json}', 'application/json')
+                    CREATE, '{some-json}', 'application/json')
                 >> { new ResponseEntity<>(HttpStatus.CREATED) }
     }
 
     def 'Get resource data for pass-through operational from DMI.'() {
         given: 'get data node is called'
-            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
-                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+            mockDataNode()
         and: 'get resource data from DMI is called'
-            mockDmiDataOperations.getResourceDataFromDmi(
-                'testCmHandle',
-                'testResourceId',
-                OPTIONS_PARAM,
-                PASSTHROUGH_OPERATIONAL,
-                NO_REQUEST_ID,
-                NO_TOPIC) >> new ResponseEntity<>('dmi-response', HttpStatus.OK)
+            mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.value,'testCmHandle',
+                    'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
+                    new ResponseEntity<>('dmi-response', HttpStatus.OK)
         when: 'get resource data operational for cm-handle is called'
-            def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
-                'testResourceId',
-                OPTIONS_PARAM,
-                NO_TOPIC,
-                NO_REQUEST_ID)
+            def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.value, 'testCmHandle',
+                    'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
         then: 'DMI returns a json response'
             response == 'dmi-response'
     }
 
     def 'Get resource data for pass-through running from DMI.'() {
         given: 'cpsDataService returns valid data node'
-            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
-                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+            mockDataNode()
         and: 'DMI returns valid response and data'
-            mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
-                'testResourceId',
-                OPTIONS_PARAM,
-                PASSTHROUGH_RUNNING,
-                NO_REQUEST_ID,
-                NO_TOPIC) >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
+            mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.value, 'testCmHandle',
+                    'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
+                    new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
         when: 'get resource data is called'
-            def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                'testResourceId',
-                OPTIONS_PARAM,
-                NO_TOPIC,
-                NO_REQUEST_ID)
+            def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.value, 'testCmHandle',
+                    'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
         then: 'get resource data returns expected response'
             response == '{dmi-response}'
     }
 
+    def 'Get bulk resource data for #datastoreName from DMI.'() {
+        given: 'cpsDataService returns valid data node'
+            mockDataNode()
+        and: 'DMI returns valid response and data'
+            mockDmiDataOperations.getResourceDataFromDmi(datastoreName, ['testCmHandle'],
+                    'testResourceId', OPTIONS_PARAM,'some topic','requestId') >>
+                    new ResponseEntity<>('{dmi-bulk-response}', HttpStatus.OK)
+        when: 'get batch resource data is called'
+            def response = objectUnderTest.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'],
+                    'testResourceId',
+                    OPTIONS_PARAM,
+                    'some topic',
+                    'requestId')
+        then: 'get bulk resource data returns expected response'
+            response == '{dmi-bulk-response}'
+        where: 'the following data stores are used'
+            datastoreName << [PASSTHROUGH_RUNNING.value, PASSTHROUGH_OPERATIONAL.value]
+    }
+
     def 'Getting Yang Resources.'() {
         when: 'yang resources is called'
             objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
@@ -242,7 +245,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 '{some-json}', 'application/json')
         then: 'DMI called with correct data'
             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
-                UPDATE, '{some-json}', 'application/json')
+                    UPDATE, '{some-json}', 'application/json')
                 >> { new ResponseEntity<>(HttpStatus.OK) }
     }
 
@@ -365,4 +368,9 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
                 .lastSyncTime('some-timestamp').build()).build()
     }
+
+    def mockDataNode() {
+        mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+    }
 }
index 90839f8..b38ca10 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,10 +34,9 @@ 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.DmiRequestBody.OperationEnum.READ
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
-
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
 
 @SpringBootTest
 @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiRestClient])
index 868ee7a..567debd 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
@@ -20,6 +20,7 @@
 
 package org.onap.cps.ncmp.api.impl.config.embeddedcache
 
+import com.hazelcast.config.Config
 import com.hazelcast.core.Hazelcast
 import com.hazelcast.map.IMap
 import org.onap.cps.spi.model.DataNode
@@ -50,8 +51,8 @@ class SynchronizationCacheConfigSpec extends Specification {
             assert null != moduleSyncStartedOnCmHandles
         and: 'system is able to create an instance of a map to hold data sync semaphores'
             assert null != dataSyncSemaphores
-        and: 'there 3 instances'
-            assert Hazelcast.allHazelcastInstances.size() == 3
+        and: 'there are at least 3 instances'
+            assert Hazelcast.allHazelcastInstances.size() > 2
         and: 'they have the correct names (in any order)'
             assert Hazelcast.allHazelcastInstances.name.containsAll('moduleSyncWorkQueue', 'moduleSyncStartedOnCmHandles', 'dataSyncSemaphores' )
     }
@@ -74,6 +75,40 @@ class SynchronizationCacheConfigSpec extends Specification {
             assert dataSyncSemaphoresConfig.asyncBackupCount == 3
     }
 
+    def 'Verify deployment network configs for Distributed objects'() {
+        given: 'the Module Sync Work Queue config'
+            def queueNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncWorkQueue').config.networkConfig
+        and: 'the Module Sync Started Cm Handle Map config'
+            def moduleSyncStartedOnCmHandlesNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncStartedOnCmHandles').config.networkConfig
+        and: 'the Data Sync Semaphores Map config'
+            def dataSyncSemaphoresNetworkConfig = Hazelcast.getHazelcastInstanceByName('dataSyncSemaphores').config.networkConfig
+        expect: 'system created instance with correct config of Module Sync Work Queue'
+            assert queueNetworkConfig.join.autoDetectionConfig.enabled
+            assert !queueNetworkConfig.join.kubernetesConfig.enabled
+        and: 'Module Sync Started Cm Handle Map has the correct settings'
+            assert moduleSyncStartedOnCmHandlesNetworkConfig.join.autoDetectionConfig.enabled
+            assert !moduleSyncStartedOnCmHandlesNetworkConfig.join.kubernetesConfig.enabled
+        and: 'Data Sync Semaphore Map has the correct settings'
+            assert dataSyncSemaphoresNetworkConfig.join.autoDetectionConfig.enabled
+            assert !dataSyncSemaphoresNetworkConfig.join.kubernetesConfig.enabled
+
+    }
+
+    def 'Verify network config'() {
+        given: 'Synchronization config object and test configuration'
+            def objectUnderTest = new SynchronizationCacheConfig()
+            def testConfig = new Config()
+        when: 'kubernetes properties are enabled'
+            objectUnderTest.cacheKubernetesEnabled = true
+            objectUnderTest.cacheKubernetesServiceName = 'test-service-name'
+        and: 'method called to update the discovery mode'
+            objectUnderTest.updateDiscoveryMode(testConfig)
+        then: 'applied properties are reflected'
+            assert testConfig.networkConfig.join.kubernetesConfig.enabled
+            assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name'
+
+    }
+
     def 'Time to Live Verify for Module Sync Semaphore'() {
         when: 'the key is inserted with a TTL of 1 second (Hazelcast TTL resolution is seconds!)'
             moduleSyncStartedOnCmHandles.put('testKeyModuleSync', 'toBeExpired' as Object, 1, TimeUnit.SECONDS)
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfigSpec.groovy
new file mode 100644 (file)
index 0000000..7448daf
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *  ============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.event.avc
+
+import com.hazelcast.config.Config
+import com.hazelcast.core.Hazelcast
+import com.hazelcast.map.IMap
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import spock.lang.Specification
+
+@SpringBootTest(classes = [ForwardedSubscriptionEventCacheConfig])
+class ForwardedSubscriptionEventCacheConfigSpec extends Specification {
+
+    @Autowired
+    private IMap<String, Set<String>> forwardedSubscriptionEventCache
+
+    def 'Embedded (hazelcast) cache for Forwarded Subscription Event Cache.'() {
+        expect: 'system is able to create an instance of the Forwarded Subscription Event Cache'
+            assert null != forwardedSubscriptionEventCache
+        and: 'there is at least 1 instance'
+            assert Hazelcast.allHazelcastInstances.size() > 0
+        and: 'Forwarded Subscription Event Cache is present'
+            assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceSubscriptionEvents')
+    }
+
+    def 'Verify configs for Distributed Caches'(){
+        given: 'the Forwarded Subscription Event Cache config'
+            def forwardedSubscriptionEventCacheConfig =  Hazelcast.getHazelcastInstanceByName('hazelCastInstanceSubscriptionEvents').config.mapConfigs.get('forwardedSubscriptionEventCacheMapConfig')
+        expect: 'system created instance with correct config'
+            assert forwardedSubscriptionEventCacheConfig.backupCount == 3
+            assert forwardedSubscriptionEventCacheConfig.asyncBackupCount == 3
+    }
+
+    def 'Verify deployment network configs for Distributed Caches'() {
+        given: 'the Forwarded Subscription Event Cache config'
+            def forwardedSubscriptionEventCacheNetworkConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceSubscriptionEvents').config.networkConfig
+        expect: 'system created instance with correct config'
+            assert forwardedSubscriptionEventCacheNetworkConfig.join.autoDetectionConfig.enabled
+            assert !forwardedSubscriptionEventCacheNetworkConfig.join.kubernetesConfig.enabled
+    }
+
+    def 'Verify network config'() {
+        given: 'Synchronization config object and test configuration'
+            def objectUnderTest = new ForwardedSubscriptionEventCacheConfig()
+            def testConfig = new Config()
+        when: 'kubernetes properties are enabled'
+            objectUnderTest.cacheKubernetesEnabled = true
+            objectUnderTest.cacheKubernetesServiceName = 'test-service-name'
+        and: 'method called to update the discovery mode'
+            objectUnderTest.updateDiscoveryMode(testConfig)
+        then: 'applied properties are reflected'
+            assert testConfig.networkConfig.join.kubernetesConfig.enabled
+            assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name'
+
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumerSpec.groovy
new file mode 100644 (file)
index 0000000..a673462
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ *  ============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.event.avc
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.hazelcast.map.IMap
+import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
+import org.onap.cps.ncmp.api.models.SubscriptionEventResponse
+import org.onap.cps.utils.JsonObjectMapper
+import org.springframework.boot.test.context.SpringBootTest
+
+@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
+class SubscriptionEventResponseConsumerSpec extends MessagingBaseSpec {
+
+    IMap<String, Set<String>> mockForwardedSubscriptionEventCache = Mock(IMap<String, Set<String>>)
+
+    def objectUnderTest = new SubscriptionEventResponseConsumer(mockForwardedSubscriptionEventCache)
+
+
+    def 'Consume Subscription Event Response where all DMIs have responded'() {
+        given: 'a subscription event response with a clientId, subscriptionName and dmiName'
+            def testEventReceived = new SubscriptionEventResponse()
+            testEventReceived.clientId = 'some-client-id'
+            testEventReceived.subscriptionName = 'some-subscription-name'
+            testEventReceived.dmiName = 'some-dmi-name'
+        and: 'notifications are enabled'
+            objectUnderTest.notificationFeatureEnabled = true
+        and: 'subscription model loader is enabled'
+            objectUnderTest.subscriptionModelLoaderEnabled = true
+        when: 'the valid event is consumed'
+            objectUnderTest.consumeSubscriptionEventResponse(testEventReceived)
+        then: 'the forwarded subscription event cache returns only the received dmiName existing for the subscription create event'
+            1 * mockForwardedSubscriptionEventCache.containsKey('some-client-idsome-subscription-name') >> true
+            1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> (['some-dmi-name'] as Set)
+        and: 'the forwarded subscription event cache returns an empty Map when the dmiName has been removed'
+            1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> ([] as Set)
+        and: 'the subscription event is removed from the map'
+            1 * mockForwardedSubscriptionEventCache.remove('some-client-idsome-subscription-name')
+    }
+
+    def 'Consume Subscription Event Response where another DMI has not yet responded'() {
+        given: 'a subscription event response with a clientId, subscriptionName and dmiName'
+            def testEventReceived = new SubscriptionEventResponse()
+            testEventReceived.clientId = 'some-client-id'
+            testEventReceived.subscriptionName = 'some-subscription-name'
+            testEventReceived.dmiName = 'some-dmi-name'
+        and: 'notifications are enabled'
+            objectUnderTest.notificationFeatureEnabled = true
+        and: 'subscription model loader is enabled'
+            objectUnderTest.subscriptionModelLoaderEnabled = true
+        when: 'the valid event is consumed'
+            objectUnderTest.consumeSubscriptionEventResponse(testEventReceived)
+        then: 'the forwarded subscription event cache returns only the received dmiName existing for the subscription create event'
+            1 * mockForwardedSubscriptionEventCache.containsKey('some-client-idsome-subscription-name') >> true
+            1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> (['some-dmi-name', 'non-responded-dmi'] as Set)
+        and: 'the forwarded subscription event cache returns an empty Map when the dmiName has been removed'
+            1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> (['non-responded-dmi'] as Set)
+        and: 'the subscription event is not removed from the map'
+            0 * mockForwardedSubscriptionEventCache.remove(_)
+    }
+}
index 3a7aa48..f2ff1f7 100644 (file)
@@ -48,7 +48,7 @@ class SubscriptionEventMapperSpec extends Specification {
             def result = objectUnderTest.toYangModelSubscriptionEvent(testEventToMap)
         then: 'the resulting yang model subscription event contains the correct clientId'
             assert result.clientId == "SCO-9989752"
-        and: 'client name'
+        and: 'subscription name'
             assert result.subscriptionName == "cm-subscription-001"
         and: 'is tagged value is false'
             assert !result.isTagged
@@ -60,4 +60,21 @@ class SubscriptionEventMapperSpec extends Specification {
             assert result.topic == null
     }
 
+    def 'Map null subscription event to yang model subscription event where #scenario'() {
+        given: 'a new Subscription Event with no data'
+            def testEventToMap = new SubscriptionEvent()
+        when: 'the event is mapped to a yang model subscription'
+            def result = objectUnderTest.toYangModelSubscriptionEvent(testEventToMap)
+        then: 'the resulting yang model subscription event contains null clientId'
+            assert result.clientId == null
+        and: 'subscription name is null'
+            assert result.subscriptionName == null
+        and: 'is tagged value is false'
+            assert result.isTagged == false
+        and: 'predicates is null'
+            assert result.predicates == null
+        and: 'the topic is null'
+            assert result.topic == null
+    }
+
 }
\ No newline at end of file
index 2b0adf3..457eb6f 100644 (file)
@@ -21,6 +21,7 @@
 package org.onap.cps.ncmp.api.impl.events.avcsubscription
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import com.hazelcast.map.IMap
 import org.onap.cps.ncmp.api.impl.events.EventsPublisher
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
@@ -29,20 +30,28 @@ import org.onap.cps.ncmp.event.model.SubscriptionEvent
 import org.onap.cps.ncmp.utils.TestUtils
 import org.onap.cps.spi.exceptions.OperationNotYetSupportedException
 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 spock.util.concurrent.BlockingVariable
 
-@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
+@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper, SubscriptionEventForwarder])
 class SubscriptionEventForwarderSpec extends MessagingBaseSpec {
 
-    def mockInventoryPersistence = Mock(InventoryPersistence)
-    def mockSubscriptionEventPublisher = Mock(EventsPublisher<SubscriptionEvent>)
-    def objectUnderTest = new SubscriptionEventForwarder(mockInventoryPersistence, mockSubscriptionEventPublisher)
+    @Autowired
+    SubscriptionEventForwarder objectUnderTest
+
+    @SpringBean
+    InventoryPersistence mockInventoryPersistence = Mock(InventoryPersistence)
+    @SpringBean
+    EventsPublisher<SubscriptionEvent> mockSubscriptionEventPublisher = Mock(EventsPublisher<SubscriptionEvent>)
+    @SpringBean
+    IMap<String, Set<String>> mockForwardedSubscriptionEventCache = Mock(IMap<String, Set<String>>)
 
     @Autowired
     JsonObjectMapper jsonObjectMapper
 
-    def 'Forward valid CM create subscription'() {
+    def 'Forward valid CM create subscription and simulate timeout where #scenario'() {
         given: 'an event'
             def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
             def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class)
@@ -52,9 +61,17 @@ class SubscriptionEventForwarderSpec extends MessagingBaseSpec {
                 createYangModelCmHandleWithDmiProperty(2, 1,"shape","square"),
                 createYangModelCmHandleWithDmiProperty(3, 2,"shape","triangle")
             ]
+        and: 'the thread creation delay is reduced to 2 seconds for testing'
+            objectUnderTest.dmiResponseTimeoutInMs = 2000
+        and: 'a Blocking Variable is used for the Asynchronous call with a timeout of 5 seconds'
+            def block = new BlockingVariable<Object>(5)
         when: 'the valid event is forwarded'
             objectUnderTest.forwardCreateSubscriptionEvent(testEventSent)
-        then: 'the event is forwarded twice with the CMHandle private properties and provides a valid listenable future'
+        then: 'An asynchronous call is made to the blocking variable'
+            block.get()
+        then: 'the event is added to the forwarded subscription event cache'
+            1 * mockForwardedSubscriptionEventCache.put("SCO-9989752cm-subscription-001", ["DMIName1", "DMIName2"] as Set)
+        and: 'the event is forwarded twice with the CMHandle private properties and provides a valid listenable future'
             1 * mockSubscriptionEventPublisher.publishEvent("ncmp-dmi-cm-avc-subscription-DMIName1", "SCO-9989752-cm-subscription-001-DMIName1",
                 subscriptionEvent -> {
                     Map targets = subscriptionEvent.getEvent().getPredicates().getTargets().get(0)
@@ -68,6 +85,15 @@ class SubscriptionEventForwarderSpec extends MessagingBaseSpec {
                     targets["CMHandle3"] == ["shape":"triangle"]
                 }
             )
+        and: 'a separate thread has been created where the map is polled'
+            1 * mockForwardedSubscriptionEventCache.containsKey("SCO-9989752cm-subscription-001") >> true
+            1 * mockForwardedSubscriptionEventCache.get(_) >> (DMINamesInMap)
+        and: 'the subscription id is removed from the event cache map returning the asynchronous blocking variable'
+            1 * mockForwardedSubscriptionEventCache.remove("SCO-9989752cm-subscription-001") >> {block.set(_)}
+        where:
+            scenario                                  | DMINamesInMap
+            'there are dmis which have not responded' | ["DMIName1", "DMIName2"] as Set
+            'all dmis have responded '                | [] as Set
     }
 
     def 'Forward CM create subscription where target CM Handles are #scenario'() {
index 03825c2..89b3a2f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,14 +30,14 @@ import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
+import org.springframework.http.HttpStatus
 import spock.lang.Shared
 
-import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
-import org.springframework.http.HttpStatus
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ
+import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
 
 @SpringBootTest
 @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiDataOperations])
@@ -50,6 +50,8 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
     def NO_REQUEST_ID = null
     @Shared
     def OPTIONS_PARAM = '(a=1,b=2)'
+    @Shared
+    def expectedBulkRequestAsJson = '{"operation": "read","data": {"fe1c1f1a070c4ce5bbfda7198592a0d3": {"neType": "RadioNode"},"b8e42eed0d9541ed8d8839e8eb86b3e0": {"neType": "RadioNode"}},"requestId": "bbb67474-f705-410a-93d1-b2844e7f45fd"}'
 
     @SpringBean
     JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
@@ -66,8 +68,8 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ) >> responseFromDmi
             dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
         when: 'get resource data is invoked'
-            def result = objectUnderTest.getResourceDataFromDmi(cmHandleId, resourceIdentifier,
-                    options, dataStore, NO_REQUEST_ID, NO_TOPIC)
+            def result = objectUnderTest.getResourceDataFromDmi(dataStore.value, cmHandleId, resourceIdentifier,
+                    options, NO_TOPIC, NO_REQUEST_ID)
         then: 'the result is the response from the DMI service'
             assert result == responseFromDmi
         where: 'the following parameters are used'
@@ -80,6 +82,25 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             'datastore running with properties'    | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING     | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running'     | '&options=(a=1,b=2)'
     }
 
+    def 'call get bulk resource data for #dataStore from DMI service with topic #scenario.'() {
+        given: 'collection of yang model cm Handles'
+            mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty])
+        and: 'a positive response from DMI service when it is called with the expected parameters'
+            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED)
+            def expectedDmiBulkResourceDataUrl = "ncmp/v1/batch/data/ds/${dataStore}?resourceIdentifier=parent/child%26options=(a=1,b=2)&topic=my-topic-name&options=(fields=schemas/schema)"
+            mockDmiRestClient.postOperationWithJsonData(expectedDmiBulkResourceDataUrl, expectedBulkRequestAsJson, READ) >> responseFromDmi
+            dmiServiceUrlBuilder.getBulkRequestUrl(_, _) >> expectedDmiBulkResourceDataUrl
+        when: 'get resource data for bulk cm handle is invoked'
+            def result = objectUnderTest.getResourceDataFromDmi( dataStore.value, [cmHandleId], resourceIdentifier,
+                    OPTIONS_PARAM,  'some-topic','requestId')
+        then: 'the result is the response from the DMI service'
+            assert result == responseFromDmi
+        where: 'the following parameters are used'
+            scenario                | dataStore
+            'datastore operational' | PASSTHROUGH_OPERATIONAL
+            'datastore running'     | PASSTHROUGH_RUNNING
+    }
+
     def 'call get all resource data.'() {
         given: 'the system returns a cm handle with a sample property'
             mockYangModelCmHandleRetrieval([yangModelCmHandleProperty])
@@ -89,7 +110,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}', READ) >> responseFromDmi
             dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
         when: 'get resource data is invoked'
-            def result = objectUnderTest.getResourceDataFromDmi(cmHandleId, PASSTHROUGH_OPERATIONAL, NO_REQUEST_ID)
+            def result = objectUnderTest.getResourceDataFromDmi( PASSTHROUGH_OPERATIONAL.value, cmHandleId, NO_REQUEST_ID)
         then: 'the result is the response from the DMI service'
             assert result == responseFromDmi
     }
index ed8f086..ed74ad3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,8 @@ package org.onap.cps.ncmp.api.impl.operations
 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.api.impl.executor.TaskExecutor
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
@@ -34,7 +36,7 @@ import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
 import spock.lang.Shared
 
-import static  org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ
+import static  org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ
 
 @SpringBootTest
 @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations])
@@ -49,6 +51,12 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
     @SpringBean
     JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
 
+    @SpringBean
+    TaskExecutor stubbedTaskExecutor = Stub()
+
+    @SpringBean
+    DmiServiceNameOrganizer stubbedDmiServiceNameOrganizer = Stub()
+
     def 'Retrieving module references.'() {
         given: 'a cm handle'
             mockYangModelCmHandleRetrieval([])
@@ -89,7 +97,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         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",
-                '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi
+                    '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi
         when: 'a get module references is called'
             def result = objectUnderTest.getModuleReferences(yangModelCmHandle)
         then: 'the result is the response from DMI service'
@@ -108,7 +116,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
                                                       [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",
-                '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ) >> responseFromDmi
+                    '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ) >> responseFromDmi
         when: 'get new yang resources from DMI service'
             def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences)
         then: 'the result has the 2 expected yang (re)sources (order is not guaranteed)'
@@ -140,7 +148,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         and: 'a positive response from DMI service when it is called with the expected parameters'
             def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK)
             mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources",
-                '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi
+                    '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi
         when: 'get new yang resources from DMI service'
             def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, unknownModuleReferences)
         then: 'the result is the response from DMI service'
index c4d0020..1b2c50a 100644 (file)
@@ -58,12 +58,21 @@ abstract class DmiOperationsBaseSpec extends Specification {
     def static resourceIdentifier = 'parent/child'
 
     def mockYangModelCmHandleRetrieval(dmiProperties) {
+        populateYangModelCmHandle(dmiProperties)
+        mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle
+    }
+
+    def mockYangModelCmHandleCollectionRetrieval(dmiProperties) {
+        populateYangModelCmHandle(dmiProperties)
+        mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle]
+    }
+
+    def populateYangModelCmHandle(dmiProperties) {
         yangModelCmHandle.dmiDataServiceName = dmiServiceName
         yangModelCmHandle.dmiServiceName = dmiServiceName
         yangModelCmHandle.dmiProperties = dmiProperties
         yangModelCmHandle.id = cmHandleId
         yangModelCmHandle.compositeState = new CompositeState()
         yangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY
-        mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle
     }
 }
index 0156988..6ca3105 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
 
 package org.onap.cps.ncmp.api.impl.utils
 
+import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService
 import org.onap.cps.spi.utils.CpsValidator
 
-import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING
 
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
@@ -45,8 +46,8 @@ class DmiServiceUrlBuilderSpec extends Specification {
     def 'Create the dmi service url with #scenario.'() {
         given: 'uri variables'
             dmiProperties.dmiBasePath = 'dmi'
-            def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle,
-                    "cmHandle", PASSTHROUGH_RUNNING)
+            def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.value, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA),
+                    "cmHandle")
         and: 'query params'
                             def uriQueries = objectUnderTest.populateQueryParams(resourceId,
                     'optionsParamInQuery', topic)
@@ -65,8 +66,8 @@ class DmiServiceUrlBuilderSpec extends Specification {
     def 'Populate dmi data store url #scenario.'() {
         given: 'uri variables are created'
             dmiProperties.dmiBasePath = dmiBasePath
-            def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle,
-                    "cmHandle", PASSTHROUGH_RUNNING)
+            def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.value, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA),
+                    "cmHandle")
         and: 'null query params'
             def uriQueries = objectUnderTest.populateQueryParams(null,
                     null, null)
index f4176d6..8164dcf 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 
 package org.onap.cps.ncmp.api.inventory.sync
 
+import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
+
 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.operations.DmiOperations
 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
 import org.onap.cps.ncmp.api.inventory.CmHandleState
 import org.onap.cps.ncmp.api.inventory.CompositeState
@@ -38,7 +39,6 @@ import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
 import spock.lang.Shared
 import spock.lang.Specification
-
 import java.time.OffsetDateTime
 import java.time.format.DateTimeFormatter
 import java.util.stream.Collectors
@@ -137,7 +137,7 @@ class SyncUtilsSpec extends Specification{
             def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}'
             JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString);
             def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK)
-            mockDmiDataOperations.getResourceDataFromDmi('cm-handle-123', DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, _) >> responseEntity
+            mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.value, 'cm-handle-123', _) >> responseEntity
         when: 'get resource data is called'
             def result = objectUnderTest.getResourceData('cm-handle-123')
         then: 'the returned data is correct'
index aa8bc53..a14a0f2 100644 (file)
@@ -32,6 +32,7 @@ import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
+import org.onap.cps.spi.model.Dataspace
 import org.springframework.boot.SpringApplication
 import org.slf4j.LoggerFactory
 import org.springframework.boot.context.event.ApplicationReadyEvent
@@ -73,9 +74,15 @@ class SubscriptionModelLoaderSpec extends Specification {
     }
 
     def 'Onboard subscription model successfully via application ready event'() {
-        when:'model loader is enabled'
+        given: 'dataspace is ready for use'
+            mockCpsAdminService.getDataspace('NCMP-Admin') >> new Dataspace('NCMP-Admin')
+        and:'model loader is enabled'
             objectUnderTest.subscriptionModelLoaderEnabled = true
-        and: 'the application is ready'
+        and: 'maximum attempt count is set'
+            objectUnderTest.maximumAttemptCount = 20
+        and: 'retry time is set'
+            objectUnderTest.retryTimeMs = 100
+        when: 'the application is ready'
             objectUnderTest.onApplicationEvent(applicationReadyEvent)
         then: 'the module service to create schema set is called once'
             1 * mockCpsModuleService.createSchemaSet('NCMP-Admin', 'subscriptions',sampleYangContentMap)
@@ -90,6 +97,8 @@ class SubscriptionModelLoaderSpec extends Specification {
             objectUnderTest.subscriptionModelLoaderEnabled = false
         and: 'application is ready'
             objectUnderTest.onApplicationEvent(applicationReadyEvent)
+        and: 'dataspace is ready for use'
+            mockCpsAdminService.getDataspace('NCMP-Admin') >> new Dataspace('NCMP-Admin')
         then: 'the module service to create schema set was not called'
             0 * mockCpsModuleService.createSchemaSet(*_)
         and: 'the admin service to create an anchor set was not called'
@@ -98,11 +107,34 @@ class SubscriptionModelLoaderSpec extends Specification {
             0 * mockCpsDataService.saveData(*_)
     }
 
+    def 'Onboard subscription model fails as NCMP dataspace does not exist' () {
+        given: 'model loader is enabled'
+            objectUnderTest.subscriptionModelLoaderEnabled = true
+        and: 'maximum attempt count is set'
+            objectUnderTest.maximumAttemptCount = 20
+        and: 'retry time is set'
+            objectUnderTest.retryTimeMs = 100
+        when: 'the application is ready'
+            objectUnderTest.onApplicationEvent(applicationReadyEvent)
+        then: 'the module service to create schema set was not called'
+            0 * mockCpsModuleService.createSchemaSet(*_)
+        and: 'the admin service to create an anchor set was not called'
+            0 * mockCpsAdminService.createAnchor(*_)
+        and: 'the data service to create a top level datanode was not called'
+            0 * mockCpsDataService.saveData(*_)
+        and: 'the log message contains the correct exception message'
+            def logs = appender.list.toString()
+            assert logs.contains("Retrieval of NCMP dataspace fails")
+    }
+
+
     def 'Exception occurred while schema set creation' () {
         given: 'creating a schema set throws an exception'
             mockCpsModuleService.createSchemaSet(*_) >>  { throw new DataValidationException(*_) }
         and: 'model loader is enabled'
             objectUnderTest.subscriptionModelLoaderEnabled = true
+        and: 'dataspace is ready for use'
+            mockCpsAdminService.getDataspace('NCMP-Admin') >> new Dataspace('NCMP-Admin')
         when: 'application is ready'
             objectUnderTest.onApplicationEvent(applicationReadyEvent)
         then: 'the admin service to create an anchor set was not called'
index e66f23d..679248b 100644 (file)
@@ -35,4 +35,11 @@ ncmp:
             parallelism-level: 3
 
     model-loader:
-        subscription: true
\ No newline at end of file
+        subscription: true
+
+# Custom Hazelcast Config.
+hazelcast:
+  mode:
+    kubernetes:
+      enabled: false
+      service-name: "cps-and-ncmp-service"
\ No newline at end of file
index 854450c..f44e310 100644 (file)
@@ -24,7 +24,7 @@ package org.onap.cps.cpspath.parser;
 import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -47,7 +47,7 @@ public class CpsPathBuilder extends CpsPathBaseListener {
 
     final CpsPathQuery cpsPathQuery = new CpsPathQuery();
 
-    final Map<String, Object> leavesData = new HashMap<>();
+    final Map<String, Object> leavesData = new LinkedHashMap<>();
 
     final StringBuilder normalizedXpathBuilder = new StringBuilder();
 
index 96fdf88..9552a4d 100644 (file)
@@ -84,6 +84,8 @@ class CpsPathQuerySpec extends Specification {
             'parent & child with more than one attribute has OR operator' | '/parent/child[@key1=1 or @key2="abc"]/child2'                 || "/parent/child[@key1='1' or @key2='abc']/child2"
             'parent & child with multiple AND  operators'                 | '/parent/child[@key1=1 and @key2="abc" and @key="xyz"]/child2' || "/parent/child[@key1='1' and @key2='abc' and @key='xyz']/child2"
             'parent & child with multiple OR  operators'                  | '/parent/child[@key1=1 or @key2="abc" or @key="xyz"]/child2'   || "/parent/child[@key1='1' or @key2='abc' or @key='xyz']/child2"
+            'parent & child with multiple AND/OR combination'             | '/parent/child[@key1=1 and @key2="abc" or @key="xyz"]/child2'  || "/parent/child[@key1='1' and @key2='abc' or @key='xyz']/child2"
+            'parent & child with multiple OR/AND combination'             | '/parent/child[@key1=1 or @key2="abc" and @key="xyz"]/child2'  || "/parent/child[@key1='1' or @key2='abc' and @key='xyz']/child2"
     }
 
     def 'Parse xpath to form the Normalized xpath containing #scenario.'() {
@@ -105,14 +107,17 @@ class CpsPathQuerySpec extends Specification {
         and: 'the right parameters are set'
             result.descendantName == "child"
             result.leavesData.size() == expectedNumberOfLeaves
+        and: 'the given operator(s) returns in the correct order'
             result.booleanOperatorsType == expectedOperators
         where: 'the following data is used'
-            scenario                                                   | cpsPath                                                                          || expectedNumberOfLeaves || expectedOperators
-            'one attribute'                                            | '//child[@common-leaf-name-int=5]'                                               || 1                      || null
-            'more than one attribute has AND operator'                 | '//child[@int-leaf=5 and @leaf-name="leaf value"]'                               || 2                      || ['and']
-            'more than one attribute has OR operator'                  | '//child[@int-leaf=5 or @leaf-name="leaf value"]'                                || 2                      || ['or']
-            'more than one attribute has combinations and/or operator' | '//child[@int-leaf=5 and @leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 2                      || ['and', 'and']
-            'more than one attribute has combinations or/and operator' | '//child[@int-leaf=5 or @leaf-name="leaf value" or @leaf-name="leaf value1" ]'   || 2                      || ['or', 'or']
+            scenario                                                      | cpsPath                                                                          || expectedNumberOfLeaves || expectedOperators
+            'one attribute'                                               | '//child[@common-leaf-name-int=5]'                                               || 1                      || null
+            'more than one attribute has AND operator'                    | '//child[@int-leaf=5 and @leaf-name="leaf value"]'                               || 2                      || ['and']
+            'more than one attribute has OR operator'                     | '//child[@int-leaf=5 or @leaf-name="leaf value"]'                                || 2                      || ['or']
+            'more than one attribute has combinations and operators'      | '//child[@int-leaf=5 and @leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 2                      || ['and', 'and']
+            'more than one attribute has combinations or operators'       | '//child[@int-leaf=5 or @leaf-name="leaf value" or @leaf-name="leaf value1" ]'   || 2                      || ['or', 'or']
+            'more than one attribute has combinations and/or combination' | '//child[@int-leaf=5 and @leaf-name="leaf value" or @leaf-name="leaf value1" ]'  || 2                      || ['and', 'or']
+            'more than one attribute has combinations or/and combination' | '//child[@int-leaf=5 or @leaf-name="leaf value" and @leaf-name="leaf value1" ]'  || 2                      || ['or', 'and']
     }
 
     def 'Parse #scenario cps path with text function condition'() {
@@ -136,7 +141,7 @@ class CpsPathQuerySpec extends Specification {
             'descendant with leaf value and ancestor' | '//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent' || true                 | true
     }
 
-    def 'Parse #scenario cps path with contains function condition'() {
+    def 'Parse cps path with contains function condition'() {
         when: 'the given cps path is parsed'
             def result = CpsPathQuery.createFrom('//someContainer[contains(@lang,"en")]')
         then: 'the query has the right xpath type'
index 369e528..3d9105f 100644 (file)
@@ -493,21 +493,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         fragmentRepository.save(fragmentEntity);
     }
 
-    @Override
-    public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
-                                             final DataNode dataNode) {
-        final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
-        final FragmentEntity fragmentEntity = getFragmentEntity(anchorEntity, dataNode.getXpath());
-        updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
-        try {
-            fragmentRepository.save(fragmentEntity);
-        } catch (final StaleStateException staleStateException) {
-            throw new ConcurrencyException("Concurrent Transactions",
-                    String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
-                            dataspaceName, anchorName, dataNode.getXpath()));
-        }
-    }
-
     @Override
     public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
                                               final Collection<DataNode> updatedDataNodes) {
index b4366de..cd1457e 100755 (executable)
@@ -159,7 +159,7 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
     @Override
     public Collection<SchemaSet> getSchemaSetsByDataspaceName(final String dataspaceName) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
-        final List<SchemaSetEntity> schemaSetEntities = schemaSetRepository.getByDataspace(dataspaceEntity);
+        final List<SchemaSetEntity> schemaSetEntities = schemaSetRepository.findByDataspace(dataspaceEntity);
         return schemaSetEntities.stream()
                 .map(CpsModulePersistenceServiceImpl::toSchemaSet).collect(Collectors.toList());
     }
index f7b586d..fe9ff9e 100755 (executable)
@@ -27,6 +27,7 @@ import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.exceptions.AnchorNotFoundException;
 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;
@@ -45,11 +46,27 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> {
 
     Collection<AnchorEntity> findAllBySchemaSet(SchemaSetEntity schemaSetEntity);
 
-    Collection<AnchorEntity> findAllByDataspaceAndNameIn(DataspaceEntity dataspaceEntity,
-                                                         Collection<String> anchorNames);
+    @Query(value = "SELECT * FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)",
+        nativeQuery = true)
+    Collection<AnchorEntity> findAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId,
+                                                           @Param("anchorNames") String[] anchorNames);
 
-    Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(DataspaceEntity dataspaceEntity,
-                                                                  Collection<String> schemaSetNames);
+    default Collection<AnchorEntity> findAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity,
+                                                                 final Collection<String> anchorNames) {
+        return findAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0]));
+    }
+
+    @Query(value = "SELECT a.* FROM anchor a"
+        + " LEFT OUTER JOIN schema_set s ON a.schema_set_id = s.id"
+        + " WHERE a.dataspace_id = :dataspaceId AND s.name = ANY (:schemaSetNames)",
+        nativeQuery = true)
+    Collection<AnchorEntity> findAllByDataspaceIdAndSchemaSetNameIn(@Param("dataspaceId") int dataspaceId,
+                                                                    @Param("schemaSetNames") String[] schemaSetNames);
+
+    default Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(final DataspaceEntity dataspaceEntity,
+                                                                          final Collection<String> schemaSetNames) {
+        return findAllByDataspaceIdAndSchemaSetNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0]));
+    }
 
     Integer countByDataspace(DataspaceEntity dataspaceEntity);
 
@@ -57,12 +74,29 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> {
         + "JOIN schema_set_yang_resources ON schema_set_yang_resources.yang_resource_id = yang_resource.id\n"
         + "JOIN schema_set ON schema_set.id = schema_set_yang_resources.schema_set_id\n"
         + "JOIN anchor ON anchor.schema_set_id = schema_set.id\n"
-        + "WHERE schema_set.dataspace_id = :dataspaceId AND module_name IN (:moduleNames)\n"
+        + "WHERE schema_set.dataspace_id = :dataspaceId AND module_name = ANY (:moduleNames)\n"
         + "GROUP BY anchor.id, anchor.name, anchor.dataspace_id, anchor.schema_set_id\n"
         + "HAVING COUNT(DISTINCT module_name) = :sizeOfModuleNames", nativeQuery = true)
     Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId,
-        @Param("moduleNames") Collection<String> moduleNames, @Param("sizeOfModuleNames") int sizeOfModuleNames);
+                                                                   @Param("moduleNames") String[] moduleNames,
+                                                                   @Param("sizeOfModuleNames") int sizeOfModuleNames);
+
+    default Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(final int dataspaceId,
+                                                                           final Collection<String> moduleNames,
+                                                                           final int sizeOfModuleNames) {
+        final String[] moduleNamesArray = moduleNames.toArray(new String[0]);
+        return getAnchorsByDataspaceIdAndModuleNames(dataspaceId, moduleNamesArray, sizeOfModuleNames);
+    }
+
+    @Modifying
+    @Query(value = "DELETE FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)",
+        nativeQuery = true)
+    void deleteAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId,
+                                         @Param("anchorNames") String[] anchorNames);
+
+    default void deleteAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity,
+                                               final Collection<String> anchorNames) {
+        deleteAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0]));
+    }
 
-    void deleteAllByDataspaceAndNameIn(DataspaceEntity dataspaceEntity,
-                                       Collection<String> anchorNames);
 }
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepository.java
deleted file mode 100644 (file)
index bad68f7..0000000
+++ /dev/null
@@ -1,49 +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.spi.repository;
-
-import java.util.Collection;
-
-/**
- * This interface is used in delete fragment entity by id with child using native sql queries.
- */
-public interface FragmentNativeRepository {
-
-    /**
-     * Delete fragment entities for each supplied xpath.
-     * This method will delete list elements or other data nodes, but not whole lists.
-     * Non-existing xpaths will not result in an exception.
-     * @param anchorId the id of the anchor
-     * @param xpaths   xpaths of data nodes to remove
-     */
-    void deleteByAnchorIdAndXpaths(int anchorId, Collection<String> xpaths);
-
-    /**
-     * Delete fragment entities that are list elements of each supplied list xpath.
-     * For example, if xpath '/parent/list' is provided, then list all elements in '/parent/list' will be deleted,
-     * e.g. /parent/list[@key='A'], /parent/list[@key='B'].
-     * This method will only delete whole lists by xpath; xpaths to list elements or other data nodes will be ignored.
-     * Non-existing xpaths will not result in an exception.
-     * @param anchorId   the id of the anchor
-     * @param listXpaths xpaths of whole lists to remove
-     */
-    void deleteListsByAnchorIdAndXpaths(int anchorId, Collection<String> listXpaths);
-}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java
deleted file mode 100644 (file)
index 04b7080..0000000
+++ /dev/null
@@ -1,72 +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.spi.repository;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.stream.Collectors;
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.Query;
-import lombok.RequiredArgsConstructor;
-
-@RequiredArgsConstructor
-public class FragmentNativeRepositoryImpl implements FragmentNativeRepository {
-
-    @PersistenceContext
-    private final EntityManager entityManager;
-
-    @Override
-    public void deleteByAnchorIdAndXpaths(final int anchorId, final Collection<String> xpaths) {
-        final String queryString =
-            "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath IN (:parameterPlaceholders))";
-        executeUpdateWithAnchorIdAndCollection(queryString, anchorId, xpaths);
-    }
-
-    @Override
-    public void deleteListsByAnchorIdAndXpaths(final int anchorId, final Collection<String> listXpaths) {
-        final Collection<String> listXpathPatterns =
-            listXpaths.stream().map(listXpath -> listXpath + "[%").collect(Collectors.toSet());
-        final String queryString =
-            "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath LIKE ANY (array[:parameterPlaceholders]))";
-        executeUpdateWithAnchorIdAndCollection(queryString, anchorId, listXpathPatterns);
-    }
-
-    // Accept security hotspot as placeholders in SQL query are created internally, not from user input.
-    @SuppressWarnings("squid:S2077")
-    private void executeUpdateWithAnchorIdAndCollection(final String sqlTemplate, final int anchorId,
-                                                        final Collection<String> collection) {
-        if (!collection.isEmpty()) {
-            final String parameterPlaceholders = String.join(",", Collections.nCopies(collection.size(), "?"));
-            final String queryStringWithParameterPlaceholders =
-                sqlTemplate.replaceFirst(":parameterPlaceholders\\b", parameterPlaceholders);
-
-            final Query query = entityManager.createNativeQuery(queryStringWithParameterPlaceholders);
-            query.setParameter(1, anchorId);
-            int parameterIndex = 2;
-            for (final String parameterValue : collection) {
-                query.setParameter(parameterIndex++, parameterValue);
-            }
-            query.executeUpdate();
-        }
-    }
-
-}
index d486a39..8114f10 100755 (executable)
@@ -37,8 +37,7 @@ import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;\r
 \r
 @Repository\r
-public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,\r
-        FragmentNativeRepository {\r
+public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery {\r
 \r
     Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);\r
 \r
@@ -52,26 +51,45 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
     @Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor")\r
     List<FragmentExtract> findAllExtractsByAnchor(@Param("anchor") AnchorEntity anchorEntity);\r
 \r
-    List<FragmentEntity> findAllByAnchorAndXpathIn(AnchorEntity anchorEntity, Collection<String> xpath);\r
+    @Query(value = "SELECT * FROM fragment WHERE xpath = ANY (:xpaths)", nativeQuery = true)\r
+    List<FragmentEntity> findAllByXpathIn(@Param("xpaths") String[] xpath);\r
 \r
-    List<FragmentEntity> findAllByXpathIn(Collection<String> xpath);\r
+    default List<FragmentEntity> findAllByXpathIn(final Collection<String> xpaths) {\r
+        return findAllByXpathIn(xpaths.toArray(new String[0]));\r
+    }\r
+\r
+    @Modifying\r
+    @Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true)\r
+    void deleteByAnchorIdIn(@Param("anchorIds") int[] anchorIds);\r
+\r
+    default void deleteByAnchorIn(final Collection<AnchorEntity> anchorEntities) {\r
+        deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).mapToInt(id -> id).toArray());\r
+    }\r
 \r
     @Modifying\r
-    @Query("DELETE FROM FragmentEntity WHERE anchor IN (:anchors)")\r
-    void deleteByAnchorIn(@Param("anchors") Collection<AnchorEntity> anchorEntities);\r
+    @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", nativeQuery = true)\r
+    void deleteByAnchorIdAndXpaths(@Param("anchorId") int anchorId, @Param("xpaths") String[] xpaths);\r
+\r
+    default void deleteByAnchorIdAndXpaths(final int 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") int anchorId,\r
+                                         @Param("xpathPatterns") String[] xpathPatterns);\r
+\r
+    default void deleteListsByAnchorIdAndXpaths(int anchorId, Collection<String> xpaths) {\r
+        final String[] listXpathPatterns = xpaths.stream().map(xpath -> xpath + "[%").toArray(String[]::new);\r
+        deleteByAnchorIdAndXpathLikeAny(anchorId, listXpathPatterns);\r
+    }\r
 \r
     @Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor"\r
         + " AND (xpath = :parentXpath OR xpath LIKE CONCAT(:parentXpath,'/%'))")\r
     List<FragmentExtract> findByAnchorAndParentXpath(@Param("anchor") AnchorEntity anchorEntity,\r
                                                      @Param("parentXpath") String parentXpath);\r
 \r
-    @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"\r
-            + " CAST(attributes AS TEXT) AS attributes"\r
-            + " FROM FRAGMENT WHERE "\r
-            + "( xpath = :parentXpath OR xpath LIKE CONCAT(:parentXpath,'/%') )",\r
-            nativeQuery = true)\r
-    List<FragmentExtract> findByParentXpath(@Param("parentXpath") String parentXpath);\r
-\r
     @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"\r
         + " CAST(attributes AS TEXT) AS attributes"\r
         + " FROM FRAGMENT WHERE anchor_id = :anchorId"\r
@@ -80,9 +98,15 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
     List<FragmentExtract> quickFindWithDescendants(@Param("anchorId") int anchorId,\r
                                                    @Param("xpathRegex") String xpathRegex);\r
 \r
-    @Query("SELECT xpath FROM FragmentEntity WHERE anchor = :anchor AND xpath IN :xpaths")\r
-    List<String> findAllXpathByAnchorAndXpathIn(@Param("anchor") AnchorEntity anchorEntity,\r
-                                                @Param("xpaths") Collection<String> xpaths);\r
+    @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",\r
+        nativeQuery = true)\r
+    List<String> findAllXpathByAnchorIdAndXpathIn(@Param("anchorId") int 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
     boolean existsByAnchorAndXpathStartsWith(AnchorEntity anchorEntity, String xpath);\r
 \r
@@ -93,7 +117,7 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
         = "WITH RECURSIVE parent_search AS ("\r
         + "  SELECT id, 0 AS depth "\r
         + "    FROM fragment "\r
-        + "   WHERE anchor_id = :anchorId AND xpath IN :xpaths "\r
+        + "   WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths) "\r
         + "   UNION "\r
         + "  SELECT c.id, depth + 1 "\r
         + "    FROM fragment c INNER JOIN parent_search p ON c.parent_id = p.id"\r
@@ -104,9 +128,14 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
         nativeQuery = true\r
     )\r
     List<FragmentExtract> findExtractsWithDescendants(@Param("anchorId") int anchorId,\r
-                                                      @Param("xpaths") Collection<String> xpaths,\r
+                                                      @Param("xpaths") String[] xpaths,\r
                                                       @Param("maxDepth") int maxDepth);\r
 \r
+    default List<FragmentExtract> findExtractsWithDescendants(final int anchorId, final Collection<String> xpaths,\r
+                                                              final int maxDepth) {\r
+        return findExtractsWithDescendants(anchorId, xpaths.toArray(new String[0]), maxDepth);\r
+    }\r
+\r
     @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"\r
             + " CAST(attributes AS TEXT) AS attributes"\r
             + " FROM FRAGMENT WHERE xpath ~ :xpathRegex",\r
index 3263f34..3c5f973 100644 (file)
@@ -24,7 +24,6 @@ package org.onap.cps.spi.repository;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
-import java.util.stream.Collectors;
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
@@ -44,7 +43,7 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte
      * @param dataspaceEntity dataspace entity
      * @return list of schema set entity
      */
-    Collection<SchemaSetEntity> findByDataspace(DataspaceEntity dataspaceEntity);
+    List<SchemaSetEntity> findByDataspace(DataspaceEntity dataspaceEntity);
 
     Integer countByDataspace(DataspaceEntity dataspaceEntity);
 
@@ -61,24 +60,20 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte
             .orElseThrow(() -> new SchemaSetNotFoundException(dataspaceEntity.getName(), schemaSetName));
     }
 
-    /**
-     * Gets all schema sets for a given dataspace.
-     *
-     * @param dataspaceEntity dataspace entity
-     * @return list of schema set entity
-     * @throws SchemaSetNotFoundException if SchemaSet not found
-     */
-    default List<SchemaSetEntity> getByDataspace(final DataspaceEntity dataspaceEntity) {
-        return findByDataspace(dataspaceEntity).stream().collect(Collectors.toList());
-    }
+    @Modifying
+    @Query(value = "DELETE FROM schema_set WHERE dataspace_id = :dataspaceId AND name = ANY (:schemaSetNames)",
+        nativeQuery = true)
+    void deleteByDataspaceIdAndNameIn(@Param("dataspaceId") final int dataspaceId,
+                                      @Param("schemaSetNames") final String[] schemaSetNames);
 
     /**
      * Delete multiple schema sets in a given dataspace.
      * @param dataspaceEntity dataspace entity
      * @param schemaSetNames  schema set names
      */
-    @Modifying
-    @Query("DELETE FROM SchemaSetEntity s WHERE s.dataspace = :dataspaceEntity AND s.name IN (:schemaSetNames)")
-    void deleteByDataspaceAndNameIn(@Param("dataspaceEntity") DataspaceEntity dataspaceEntity,
-                                    @Param("schemaSetNames") Collection<String> schemaSetNames);
+    default void deleteByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity,
+                                            final Collection<String> schemaSetNames) {
+        deleteByDataspaceIdAndNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0]));
+    }
+
 }
index fff0a6a..7584ff6 100644 (file)
@@ -35,7 +35,11 @@ import org.springframework.stereotype.Repository;
 public interface YangResourceRepository extends JpaRepository<YangResourceEntity, Long>,
     YangResourceNativeRepository, SchemaSetYangResourceRepository {
 
-    List<YangResourceEntity> findAllByChecksumIn(Set<String> checksum);
+    List<YangResourceEntity> findAllByChecksumIn(String[] checksums);
+
+    default List<YangResourceEntity> findAllByChecksumIn(final Collection<String> checksums) {
+        return findAllByChecksumIn(checksums.toArray(new String[0]));
+    }
 
     @Query(value = "SELECT DISTINCT\n"
         + "yang_resource.module_name AS module_name,\n"
@@ -86,9 +90,14 @@ public interface YangResourceRepository extends JpaRepository<YangResourceEntity
         + "schema_set.id\n"
         + "JOIN yang_resource ON yang_resource.id = schema_set_yang_resources.yang_resource_id\n"
         + "WHERE\n"
-        + "dataspace.name = :dataspaceName and yang_resource.module_Name IN (:moduleNames)", nativeQuery = true)
+        + "dataspace.name = :dataspaceName and yang_resource.module_Name = ANY (:moduleNames)", nativeQuery = true)
     Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames(
-            @Param("dataspaceName") String dataspaceName, @Param("moduleNames") Collection<String> moduleNames);
+            @Param("dataspaceName") String dataspaceName, @Param("moduleNames") String[] moduleNames);
+
+    default Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames(
+        final String dataspaceName, final Collection<String> moduleNames) {
+        return findAllModuleReferencesByDataspaceAndModuleNames(dataspaceName, moduleNames.toArray(new String[0]));
+    }
 
     @Modifying
     @Query(value = "DELETE FROM yang_resource yr WHERE NOT EXISTS "
index e60afa7..93d7662 100755 (executable)
@@ -23,7 +23,6 @@
 
 package org.onap.cps.spi.impl
 
-import com.fasterxml.jackson.databind.ObjectMapper
 import com.google.common.collect.ImmutableSet
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.entities.FragmentEntity
@@ -35,7 +34,6 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
-import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.test.context.jdbc.Sql
 
@@ -49,7 +47,6 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     @Autowired
     CpsDataPersistenceService objectUnderTest
 
-    static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
     static DataNodeBuilder dataNodeBuilder = new DataNodeBuilder()
 
     static final String SET_DATA = '/data/fragment.sql'
@@ -353,11 +350,11 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Update data node and descendants by removing descendants.'() {
-        given: 'data node object with leaves updated, no children'
-            def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [])
+    def 'Update data nodes and descendants by removing descendants.'() {
+        given: 'data nodes with leaves updated, no children'
+            def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [])]
         when: 'update data nodes and descendants is performed'
-            objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
+            objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes)
         then: 'leaves have been updated for selected data node'
             def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID)
             def updatedLeaves = getLeavesMap(updatedFragment)
@@ -370,13 +367,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Update data node and descendants with new descendants'() {
-        given: 'data node object with leaves updated, having child with old content'
-            def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
+    def 'Update data nodes and descendants with new descendants'() {
+        given: 'data nodes with leaves updated, having child with old content'
+            def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
                   buildDataNode('/parent-200/child-201/grand-child', ['leaf-value': 'original'], [])
-            ])
+            ])]
         when: 'update is performed including descendants'
-            objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
+            objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes)
         then: 'leaves have been updated for selected data node'
             def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID)
             def updatedLeaves = getLeavesMap(updatedFragment)
@@ -390,13 +387,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Update data node and descendants with same descendants but changed leaf value.'() {
-        given: 'data node object with leaves updated, having child with old content'
-            def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
+    def 'Update data nodes and descendants with same descendants but changed leaf value.'() {
+        given: 'data nodes with leaves updated, having child with old content'
+            def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
                     buildDataNode('/parent-200/child-201/grand-child', ['leaf-value': 'new'], [])
-            ])
+            ])]
         when: 'update is performed including descendants'
-            objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
+            objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes)
         then: 'leaves have been updated for selected data node'
             def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID)
             def updatedLeaves = getLeavesMap(updatedFragment)
@@ -410,13 +407,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Update data node and descendants with different descendants xpath'() {
-        given: 'data node object with leaves updated, having child with old content'
-            def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
+    def 'Update data nodes and descendants with different descendants xpath'() {
+        given: 'data nodes with leaves updated, having child with old content'
+            def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
                     buildDataNode('/parent-200/child-201/grand-child-new', ['leaf-value': 'new'], [])
-            ])
+            ])]
         when: 'update is performed including descendants'
-            objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
+            objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes)
         then: 'leaves have been updated for selected data node'
             def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID)
             def updatedLeaves = getLeavesMap(updatedFragment)
@@ -432,19 +429,17 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Update data node and descendants error scenario: #scenario.'() {
-        given: 'data node object'
-            def submittedDataNode = buildDataNode(xpath, ['leaf-name': 'leaf-value'], [])
+    def 'Update data nodes and descendants error scenario: #scenario.'() {
+        given: 'data nodes collection'
+            def submittedDataNodes = [buildDataNode(xpath, ['leaf-name': 'leaf-value'], [])]
         when: 'attempt to update data node for #scenario'
-            objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, submittedDataNode)
+            objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, submittedDataNodes)
         then: 'a #expectedException is thrown'
             thrown(expectedException)
         where: 'the following data is used'
             scenario                 | dataspaceName  | anchorName                        | xpath                 || expectedException
             'non-existing dataspace' | 'NO DATASPACE' | 'not relevant'                    | '/not relevant'       || DataspaceNotFoundException
             'non-existing anchor'    | DATASPACE_NAME | 'NO ANCHOR'                       | '/not relevant'       || AnchorNotFoundException
-            'non-existing xpath'     | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | '/NON-EXISTING-XPATH' || DataNodeNotFoundException
-            'invalid xpath'          | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'INVALID XPATH'       || CpsPathException
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
@@ -699,7 +694,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
         return dataNodeBuilder.withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
     }
 
-    static Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) {
+    Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) {
         return jsonObjectMapper.convertJsonString(fragmentEntity.attributes, Map<String, Object>.class)
     }
 
index f02aa75..204b934 100644 (file)
@@ -77,24 +77,6 @@ class CpsDataPersistenceServiceSpec extends Specification {
             1 * objectUnderTest.storeDataNodes('dataspace1', 'anchor1', [dataNode])
     }
 
-    def 'Handling of StaleStateException (caused by concurrent updates) during update data node and descendants.'() {
-        given: 'the fragment repository returns a fragment entity'
-            mockFragmentRepository.getByAnchorAndXpath(*_) >> {
-                def fragmentEntity = new FragmentEntity()
-                fragmentEntity.setChildFragments([new FragmentEntity()] as Set<FragmentEntity>)
-                return fragmentEntity
-            }
-        and: 'a data node is concurrently updated by another transaction'
-            mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
-        when: 'attempt to update data node with submitted data nodes'
-            objectUnderTest.updateDataNodeAndDescendants('some-dataspace', 'some-anchor', new DataNodeBuilder().withXpath('/some/xpath').build())
-        then: 'concurrency exception is thrown'
-            def concurrencyException = thrown(ConcurrencyException)
-            assert concurrencyException.getDetails().contains('some-dataspace')
-            assert concurrencyException.getDetails().contains('some-anchor')
-            assert concurrencyException.getDetails().contains('/some/xpath')
-    }
-
     def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() {
         given: 'the system can update one datanode and has two more datanodes that throw an exception while updating'
             def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([
index 214fd69..65d63df 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Bell Canada.
+ *  Modifications 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.
  */
 package org.onap.cps.spi.impl
 
-import com.fasterxml.jackson.databind.ObjectMapper
+
 import org.hibernate.exception.ConstraintViolationException
-import org.onap.cps.spi.CpsAdminPersistenceService
 import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.spi.entities.DataspaceEntity
 import org.onap.cps.spi.exceptions.DuplicatedYangResourceException
 import org.onap.cps.spi.model.ModuleReference
-import org.onap.cps.spi.repository.AnchorRepository
 import org.onap.cps.spi.repository.DataspaceRepository
-import org.onap.cps.spi.repository.SchemaSetRepository
 import org.onap.cps.spi.repository.YangResourceRepository
-import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.dao.DataIntegrityViolationException
@@ -43,24 +40,12 @@ class CpsModulePersistenceServiceConcurrencySpec extends CpsPersistenceSpecBase
     @Autowired
     CpsModulePersistenceService objectUnderTest
 
-    @Autowired
-    AnchorRepository anchorRepository
-
-    @Autowired
-    SchemaSetRepository schemaSetRepository
-
-    @Autowired
-    CpsAdminPersistenceService cpsAdminPersistenceService
-
     @SpringBean
     YangResourceRepository yangResourceRepositoryMock = Mock()
 
     @SpringBean
     DataspaceRepository dataspaceRepositoryMock = Mock()
 
-    @SpringBean
-    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
-
     static final String DATASPACE_NAME = 'DATASPACE-001'
     static final String SCHEMA_SET_NAME_NEW = 'SCHEMA-SET-NEW'
     static final String NEW_RESOURCE_NAME = 'some new resource'
index 864b3e3..53f42f5 100644 (file)
@@ -30,31 +30,22 @@ import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 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.AnchorRepository
 import org.onap.cps.spi.repository.SchemaSetRepository
 import org.onap.cps.spi.repository.SchemaSetYangResourceRepositoryImpl
-import org.onap.cps.spi.repository.YangResourceRepository
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.test.context.jdbc.Sql
-import spock.lang.Ignore
 
 class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
 
     @Autowired
     CpsModulePersistenceService objectUnderTest
 
-    @Autowired
-    AnchorRepository anchorRepository
-
     @Autowired
     SchemaSetRepository schemaSetRepository
 
     @Autowired
     CpsAdminPersistenceService cpsAdminPersistenceService
 
-    @Autowired
-    YangResourceRepository yangResourceRepository
-
     final static String SET_DATA = '/data/schemaset.sql'
 
     def static EXISTING_SCHEMA_SET_NAME = SCHEMA_SET_NAME1
index 9ef9732..811c329 100644 (file)
@@ -84,7 +84,7 @@ class CpsModulePersistenceServiceSpec extends Specification {
 
     def 'Store schema set error scenario: #scenario.'() {
         given: 'no yang resource are currently saved'
-            yangResourceRepositoryMock.findAllByChecksumIn(_) >> Collections.emptyList()
+            yangResourceRepositoryMock.findAllByChecksumIn(_ as Collection<String>) >> Collections.emptyList()
         and: 'persisting yang resource raises db constraint exception (in case of concurrent requests for example)'
             yangResourceRepositoryMock.saveAll(_) >> { throw dbException }
         when: 'attempt to store schema set '
index 9b722cd..222a828 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
@@ -24,7 +24,6 @@ import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.spi.entities.SchemaSetEntity
 import org.onap.cps.spi.impl.CpsPersistenceSpecBase
 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.springframework.beans.factory.annotation.Autowired
@@ -52,9 +51,6 @@ class CpsModuleReferenceRepositoryPerfTest extends CpsPersistenceSpecBase {
     @Autowired
     CpsModulePersistenceService objectUnderTest
 
-    @Autowired
-    DataspaceRepository dataspaceRepository
-
     @Autowired
     SchemaSetRepository schemaSetRepository
 
index 54d6ff3..5ee6b38 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
 
 package org.onap.cps.cache;
 
-import com.hazelcast.config.Config;
 import com.hazelcast.config.MapConfig;
-import com.hazelcast.config.NamedConfig;
-import com.hazelcast.core.Hazelcast;
-import com.hazelcast.core.HazelcastInstance;
 import com.hazelcast.map.IMap;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -33,7 +29,7 @@ import org.springframework.context.annotation.Configuration;
  * Core infrastructure of the hazelcast distributed cache for anchor data config use cases.
  */
 @Configuration
-public class AnchorDataCacheConfig {
+public class AnchorDataCacheConfig extends HazelcastCacheConfig {
 
     private static final MapConfig anchorDataCacheMapConfig = createMapConfig("anchorDataCacheMapConfig");
 
@@ -44,27 +40,7 @@ public class AnchorDataCacheConfig {
      */
     @Bean
     public IMap<String, AnchorDataCacheEntry> anchorDataCache() {
-        return createHazelcastInstance("hazelCastInstanceCpsCore", anchorDataCacheMapConfig)
-                .getMap("anchorDataCache");
+        return createHazelcastInstance("hazelCastInstanceCpsCore", anchorDataCacheMapConfig, "cps-service-caches")
+            .getMap("anchorDataCache");
     }
-
-    private HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName,
-            final NamedConfig namedConfig) {
-        return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig));
-    }
-
-    private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) {
-        final Config config = new Config(instanceName);
-        config.addMapConfig((MapConfig) namedConfig);
-        config.setClusterName("cps-service-caches");
-        return config;
-    }
-
-    private static MapConfig createMapConfig(final String configName) {
-        final MapConfig mapConfig = new MapConfig(configName);
-        mapConfig.setBackupCount(3);
-        mapConfig.setAsyncBackupCount(3);
-        return mapConfig;
-    }
-
 }
diff --git a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java
new file mode 100644 (file)
index 0000000..4aebcea
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * ============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.cache;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.config.NamedConfig;
+import com.hazelcast.config.QueueConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Core infrastructure of the hazelcast distributed cache.
+ */
+@Slf4j
+public class HazelcastCacheConfig {
+
+    @Value("${hazelcast.mode.kubernetes.enabled}")
+    protected boolean cacheKubernetesEnabled;
+
+    @Value("${hazelcast.mode.kubernetes.service-name}")
+    protected String cacheKubernetesServiceName;
+
+    protected HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName,
+                                                        final NamedConfig namedConfig, final String clusterName) {
+        return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig, clusterName));
+    }
+
+    private Config initializeConfig(final String instanceName, final NamedConfig namedConfig,
+                                    final String clusterName) {
+        final Config config = new Config(instanceName);
+        if (namedConfig instanceof MapConfig) {
+            config.addMapConfig((MapConfig) namedConfig);
+        }
+        if (namedConfig instanceof QueueConfig) {
+            config.addQueueConfig((QueueConfig) namedConfig);
+        }
+        config.setClusterName(clusterName);
+        updateDiscoveryMode(config);
+        return config;
+    }
+
+    protected static MapConfig createMapConfig(final String configName) {
+        final MapConfig mapConfig = new MapConfig(configName);
+        mapConfig.setBackupCount(3);
+        mapConfig.setAsyncBackupCount(3);
+        return mapConfig;
+    }
+
+    protected static QueueConfig createQueueConfig(final String configName) {
+        final QueueConfig commonQueueConfig = new QueueConfig(configName);
+        commonQueueConfig.setBackupCount(3);
+        commonQueueConfig.setAsyncBackupCount(3);
+        return commonQueueConfig;
+    }
+
+    protected void updateDiscoveryMode(final Config config) {
+        if (cacheKubernetesEnabled) {
+            log.info("Enabling kubernetes mode with service-name : {}", cacheKubernetesServiceName);
+            config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(true)
+                .setProperty("service-name", cacheKubernetesServiceName);
+        }
+    }
+
+}
index 5404019..949fbc2 100644 (file)
@@ -137,15 +137,6 @@ public interface CpsDataPersistenceService {
      */
     void updateDataLeaves(String dataspaceName, String anchorName, String xpath, Map<String, Serializable> leaves);
 
-    /**
-     * Replaces an existing data node's content including descendants.
-     *
-     * @param dataspaceName dataspace name
-     * @param anchorName    anchor name
-     * @param dataNode      data node
-     */
-    void updateDataNodeAndDescendants(String dataspaceName, String anchorName, DataNode dataNode);
-
     /**
      * Replaces multiple existing data nodes' content including descendants in a batch operation.
      *
index 839444b..76b5345 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
@@ -19,6 +19,8 @@
  */
 
 package org.onap.cps.cache
+
+import com.hazelcast.config.Config
 import com.hazelcast.core.Hazelcast
 import com.hazelcast.map.IMap
 import org.springframework.beans.factory.annotation.Autowired
@@ -49,4 +51,28 @@ class AnchorDataCacheConfigSpec extends Specification {
             assert anchorDataCacheConfig.backupCount == 3
             assert anchorDataCacheConfig.asyncBackupCount == 3
     }
+
+    def 'Verify deployment network configs for Distributed Caches'() {
+        given: 'the Anchor Data Cache config'
+            def anchorDataCacheNetworkConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsCore').config.networkConfig
+        expect: 'system created instance with correct config'
+            assert anchorDataCacheNetworkConfig.join.autoDetectionConfig.enabled
+            assert !anchorDataCacheNetworkConfig.join.kubernetesConfig.enabled
+    }
+
+    def 'Verify network config'() {
+        given: 'Synchronization config object and test configuration'
+            def objectUnderTest = new AnchorDataCacheConfig()
+            def testConfig = new Config()
+        when: 'kubernetes properties are enabled'
+            objectUnderTest.cacheKubernetesEnabled = true
+            objectUnderTest.cacheKubernetesServiceName = 'test-service-name'
+        and: 'method called to update the discovery mode'
+            objectUnderTest.updateDiscoveryMode(testConfig)
+        then: 'applied properties are reflected'
+            assert testConfig.networkConfig.join.kubernetesConfig.enabled
+            assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name'
+
+    }
+
 }
index 04295eb..21f3745 100644 (file)
@@ -48,3 +48,10 @@ spring:
 logging:
   level:
     org.apache.kafka: ERROR
+
+# Custom Hazelcast Config.
+hazelcast:
+  mode:
+    kubernetes:
+      enabled: false
+      service-name: "cps-and-ncmp-service"
index 2018516..5f290cd 100644 (file)
@@ -1,7 +1,7 @@
 {
   "request": {
     "method": "POST",
-    "urlPattern": "/dmi/v1/ch/batch/data/ds/.*"
+    "urlPattern": "/dmi/v1/batch/data/ds/.*"
   },
   "response": {
     "status": 200,
index a24bf1e..08892e0 100644 (file)
@@ -223,7 +223,7 @@ descendant-path
 leaf-conditions
 ---------------
 
-**Syntax**: ``<xpath> '[' @<leaf-name1> '=' <leaf-value1> ( ' and ' @<leaf-name> '=' <leaf-value> )* ']'``
+**Syntax**: ``<xpath> '[' @<leaf-name1> '=' <leaf-value1> ( ' <and|or> ' @<leaf-name> '=' <leaf-value> )* ']'``
   - ``xpath``: Absolute or descendant or xpath to the (list) node which elements will be queried.
   - ``leaf-name``: The name of the leaf which value needs to be compared.
   - ``leaf-value``: The required value of the leaf.
@@ -233,10 +233,13 @@ leaf-conditions
   - ``//categories[@name="Kids"]``
   - ``//categories[@name='Kids']``
   - ``//categories[@code='1']/books/book[@title='Dune' and @price=5]``
+  - ``//categories[@code='1']/books/book[@title='xyz' or @price=15]``
   - ``//categories[@code=1]``
 **Limitations**
   - Only the last list or container can be queried leaf values. Any ancestor list will have to be referenced by its key name-value pair(s).
-  - Multiple attributes can only be combined using ``and``. ``or`` and bracketing is not supported.
+  - When mixing ``and/or`` operators, ``and`` has precedence over ``or`` . So ``and`` operators get evaluated first.
+  - Bracketing is not supported.
+  - Leaf names are not validated so ``or`` operations with invalid leaf names will silently be ignored.
   - Only leaves can be used, leaf-list are not supported.
   - Only string and integer values are supported, boolean and float values are not supported.
   - The key should be supplied with correct data type for it to be queried from DB. In the last example above the attribute code is of type
index 44287b2..a04302f 100644 (file)
@@ -62,19 +62,21 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
         and: 'the cps-path of queryDataNodes has the expectedLeaves'
             assert result.leaves.sort() == expectedLeaves.sort()
         where: 'the following data is used'
-            scenario                                | cpspath                                                    || expectedResultSize | expectedLeaves
-            'the "OR" condition'                    | '//books[@lang="English" or @price=15]'                    || 6                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
-                                                                                                                                          [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]],
-                                                                                                                                          [lang: "English", price: 14, title: "The Light Fantastic", authors: ["Terry Pratchett"], editions: [1986]],
-                                                                                                                                          [lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]],
-                                                                                                                                          [lang: "English", price: 12, title: "The Colour of Magic", authors: ["Terry Pratchett"], editions: [1983]],
-                                                                                                                                          [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]]]
-            'the "OR" condition with non-json data' | '//books[@title="xyz" or @price=15]'                       || 2                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
-                                                                                                                                          [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
-            'combination of multiple AND'           | '//books[@lang="English" and @price=15 and @edition=1983]' || 0                  | []
-            'combination of multiple OR'            | '//books[ @title="Matilda" or @price=15 or @edition=1983]' || 3                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
-                                                                                                                                          [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]],
-                                                                                                                                          [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
+            scenario                                | cpspath                                                          || expectedResultSize | expectedLeaves
+            'the "OR" condition'                    | '//books[@lang="English" or @price=15]'                          || 6                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
+                                                                                                                                                [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]],
+                                                                                                                                                [lang: "English", price: 14, title: "The Light Fantastic", authors: ["Terry Pratchett"], editions: [1986]],
+                                                                                                                                                [lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]],
+                                                                                                                                                [lang: "English", price: 12, title: "The Colour of Magic", authors: ["Terry Pratchett"], editions: [1983]],
+                                                                                                                                                [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]]]
+            'the "OR" condition with non-json data' | '//books[@title="xyz" or @price=15]'                             || 2                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
+                                                                                                                                                [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
+            'combination of multiple AND'           | '//books[@lang="English" and @price=15 and @edition=1983]'       || 0                  | []
+            'combination of multiple OR'            | '//books[ @title="Matilda" or @price=15 or @edition=1983]'       || 3                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
+                                                                                                                                                [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]],
+                                                                                                                                                [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
+            'combination of AND/OR'                 | '//books[@edition=1983 and @price=15 or @title="Good Omens"]'    || 1                  | [[lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]]]
+            'combination of OR/AND'                 | '//books[@title="Annihilation" or @price=39 and @lang="arabic"]' || 1                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]]]
     }
 
     def 'Cps Path query for leaf value(s) with #scenario.'() {
@@ -169,17 +171,19 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             def bookTitles = result.collect { it.getLeaves().get('title') }
             assert bookTitles.sort() == expectedBookTitles.sort()
         where: 'the following data is used'
-            scenario                                                   | cpsPath                                                    || expectedBookTitles
-            'one leaf'                                                 | '//books[@price=14]'                                       || ['The Light Fantastic']
-            'one text'                                                 | '//books/authors[text()="Terry Pratchett"]'                || ['Good Omens', 'The Colour of Magic', 'The Light Fantastic']
-            'more than one leaf'                                       | '//books[@price=12 and @lang="English"]'                   || ['The Colour of Magic']
-            'more than one leaf has "OR" condition'                    | '//books[@lang="English" or @price=15]'                    || ['Annihilation', 'Good Omens', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic']
-            'more than one leaf has "OR" condition with non-json data' | '//books[@title="xyz" or @price=13]'                       || ['Good Omens']
-            'more than one leaf has multiple AND'                      | '//books[@lang="English" and @price=13 and @edition=1983]' || []
-            'more than one leaf has multiple OR'                       | '//books[ @title="Matilda" or @price=15 or @edition=2006]' || ['Annihilation', 'Matilda', 'The Gruffalo']
-            'leaves reversed in order'                                 | '//books[@lang="English" and @price=12]'                   || ['The Colour of Magic']
-            'leaf and text'                                            | '//books[@price=14]/authors[text()="Terry Pratchett"]'     || ['The Light Fantastic']
-            'leaf and contains'                                        | '//books[contains(@price,"13")]'                           || ['Good Omens']
+            scenario                                                   | cpsPath                                                                || expectedBookTitles
+            'one leaf'                                                 | '//books[@price=14]'                                                   || ['The Light Fantastic']
+            'one text'                                                 | '//books/authors[text()="Terry Pratchett"]'                            || ['Good Omens', 'The Colour of Magic', 'The Light Fantastic']
+            'more than one leaf'                                       | '//books[@price=12 and @lang="English"]'                               || ['The Colour of Magic']
+            'more than one leaf has "OR" condition'                    | '//books[@lang="English" or @price=15]'                                || ['Annihilation', 'Good Omens', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic']
+            'more than one leaf has "OR" condition with non-json data' | '//books[@title="xyz" or @price=13]'                                   || ['Good Omens']
+            'more than one leaf has multiple AND'                      | '//books[@lang="English" and @price=13 and @edition=1983]'             || []
+            'more than one leaf has multiple OR'                       | '//books[ @title="Matilda" or @price=15 or @edition=2006]'             || ['Annihilation', 'Matilda', 'The Gruffalo']
+            'leaves reversed in order'                                 | '//books[@lang="English" and @price=12]'                               || ['The Colour of Magic']
+            'more than one leaf has combination of AND/OR'             | '//books[@edition=1983 and @price=13 or @title="Good Omens"]'          || ['Good Omens']
+            'more than one leaf has OR/AND'                            | '//books[@title="The Light Fantastic" or @price=11 and @edition=1983]' || ['The Light Fantastic']
+            'leaf and text'                                            | '//books[@price=14]/authors[text()="Terry Pratchett"]'                 || ['The Light Fantastic']
+            'leaf and contains'                                        | '//books[contains(@price,"13")]'                                       || ['Good Omens']
     }
 
     def 'Cps Path query using descendant anywhere with #scenario condition(s) for a list element.'() {
index 7875cae..0034af4 100644 (file)
@@ -22,7 +22,6 @@ package org.onap.cps.integration.performance.cps
 
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.integration.performance.base.CpsPerfTestBase
-import org.springframework.dao.DataAccessResourceFailureException
 
 class CpsAdminServiceLimits extends CpsPerfTestBase {
 
@@ -32,20 +31,20 @@ class CpsAdminServiceLimits extends CpsPerfTestBase {
 
     def 'Get anchors from multiple schema set names limit exceeded: 32,766 (~ 2^15) schema set names.'() {
         given: 'more than 32,766 schema set names'
-            def schemaSetNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
+            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)
-        then: 'a database exception is thrown'
-            thrown(DataAccessResourceFailureException.class)
+        then: 'a database exception is not thrown'
+            noExceptionThrown()
     }
 
     def 'Querying anchor names limit exceeded: 32,766 (~ 2^15) modules.'() {
         given: 'more than 32,766 module names'
-            def moduleNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
+            def moduleNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
         when: 'single query is executed to get all the anchors'
             objectUnderTest.queryAnchorNames(CPS_PERFORMANCE_TEST_DATASPACE, moduleNames)
-        then: 'a database exception is thrown'
-            thrown(DataAccessResourceFailureException.class)
+        then: 'a database exception is not thrown'
+            noExceptionThrown()
     }
 
 }
index 2df9101..1579470 100644 (file)
@@ -23,8 +23,7 @@ package org.onap.cps.integration.performance.cps
 import java.time.OffsetDateTime
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.integration.performance.base.CpsPerfTestBase
-import org.springframework.dao.DataAccessResourceFailureException
-import org.springframework.transaction.TransactionSystemException
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 
@@ -36,29 +35,29 @@ class CpsDataServiceLimits extends CpsPerfTestBase {
 
     def 'Multiple get limit exceeded: 32,764 (~ 2^15) xpaths.'() {
         given: 'more than 32,764 xpaths'
-            def xpaths = (0..32_764).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
+            def xpaths = (0..40_000).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
         when: 'single operation is executed to get all datanodes with given xpaths'
             objectUnderTest.getDataNodesForMultipleXpaths(CPS_PERFORMANCE_TEST_DATASPACE, 'bookstore1', xpaths, INCLUDE_ALL_DESCENDANTS)
-        then: 'a database exception is thrown'
-            thrown(DataAccessResourceFailureException.class)
+        then: 'a database exception is not thrown'
+            noExceptionThrown()
     }
 
     def 'Delete multiple datanodes limit exceeded: 32,767 (~ 2^15) xpaths.'() {
         given: 'more than 32,767 xpaths'
-            def xpaths = (0..32_767).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
+            def xpaths = (0..40_000).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
         when: 'single operation is executed to delete all datanodes with given xpaths'
             objectUnderTest.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'bookstore1', xpaths, OffsetDateTime.now())
-        then: 'a database exception is thrown'
-            thrown(TransactionSystemException.class)
+        then: 'a database exception is not thrown (but a CPS DataNodeNotFoundException is thrown)'
+            thrown(DataNodeNotFoundException.class)
     }
 
     def 'Delete datanodes from multiple anchors limit exceeded: 32,766 (~ 2^15) anchors.'() {
         given: 'more than 32,766 anchor names'
-            def anchorNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
+            def anchorNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
         when: 'single operation is executed to delete all datanodes in given anchors'
             objectUnderTest.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, anchorNames, OffsetDateTime.now())
-        then: 'a database exception is thrown'
-            thrown(DataAccessResourceFailureException.class)
+        then: 'a database exception is not thrown'
+            noExceptionThrown()
     }
 
 }