diff options
28 files changed, 1191 insertions, 293 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index e3ffd04d7b..b5b10b0f74 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -98,6 +98,8 @@ app: ncmp:
async-m2m:
topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
+ avc:
+ subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:cm-avc-subscription}
lcm:
events:
topic: ${LCM_EVENTS_TOPIC:ncmp-events}
diff --git a/cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json b/cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json new file mode 100644 index 0000000000..5ab446cbbe --- /dev/null +++ b/cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.events:avc-subscription-event:v1", + "$ref": "#/definitions/SubscriptionEvent", + "definitions": { + "SubscriptionEvent": { + "description": "The payload for avc subscription event.", + "type": "object", + "properties": { + "version": { + "description": "The event type version", + "type": "string" + }, + "eventType": { + "description": "The event type", + "type": "string", + "enum": ["CREATE"] + }, + "event": { + "$ref": "#/definitions/event" + } + }, + "required": [ + "version", + "eventContent" + ], + "additionalProperties": false + }, + "event": { + "description": "The event content.", + "type": "object", + "properties": { + "subscription": { + "description": "The subscription details.", + "type": "object", + "properties": { + "clientID": { + "description": "The clientID", + "type": "string" + }, + "name": { + "description": "The name of the subscription", + "type": "string" + }, + "isTagged": { + "description": "optional parameter, default is no", + "type": "boolean", + "default": false + } + }, + "required": [ + "clientID", + "name" + ] + }, + "dataType": { + "description": "The datatype content.", + "type": "object", + "properties": { + "dataspace": { + "description": "The dataspace name", + "type": "string" + }, + "dataCategory": { + "description": "The category type of the data", + "type": "string" + }, + "dataProvider": { + "description": "The provider name of the data", + "type": "string" + }, + "schemaName": { + "description": "The name of the schema", + "type": "string" + }, + "schemaVersion": { + "description": "The version of the schema", + "type": "string" + } + } + }, + "required": [ + "dataspace", + "dataCategory", + "dataProvider", + "schemaName", + "schemaVersion" + ], + "predicates": { + "description": "Additional values to be added into the subscription", + "existingJavaType" : "java.util.Map<String,Object>", + "type" : "object" + } + } + }, + "required": [ + "subscription", + "dataType" + ] + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java new file mode 100644 index 0000000000..1f0324693a --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java @@ -0,0 +1,53 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.event.avc; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.event.model.SubscriptionEvent; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + + +@Component +@Slf4j +@RequiredArgsConstructor +public class SubscriptionEventConsumer { + + /** + * Consume the specified event. + * + * @param subscriptionEvent the event to be consumed + */ + @KafkaListener(topics = "${app.ncmp.avc.subscription-topic}") + public void consumeSubscriptionEvent(final SubscriptionEvent subscriptionEvent) { + if ("CM".equals(subscriptionEvent.getEvent().getDataType().getDataCategory())) { + log.debug("Consuming event {} ...", subscriptionEvent.toString()); + if ("CREATE".equals(subscriptionEvent.getEventType().value())) { + log.info("Subscription for ClientID {} with name{} ...", + subscriptionEvent.getEvent().getSubscription().getClientID(), + subscriptionEvent.getEvent().getSubscription().getName()); + } + } else { + log.trace("Non-CM subscription event ignored"); + } + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy new file mode 100644 index 0000000000..20d60e3963 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy @@ -0,0 +1,52 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2022 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.event.avc + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec +import org.onap.cps.ncmp.event.model.SubscriptionEvent +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest(classes = [SubscriptionEventConsumer, ObjectMapper, JsonObjectMapper]) +class SubscriptionEventConsumerSpec extends MessagingBaseSpec { + + def objectUnderTest = new SubscriptionEventConsumer() + + @Autowired + JsonObjectMapper jsonObjectMapper + + def 'Consume valid message'() { + given: 'an event' + def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json') + def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class) + and: 'dataCategory is set' + testEventSent.getEvent().getDataType().setDataCategory(dataCategory) + when: 'the valid event is consumed' + objectUnderTest.consumeSubscriptionEvent(testEventSent) + then: 'no exception is thrown' + noExceptionThrown() + where: 'data category is changed' + dataCategory << [ 'CM' , 'FM' ] + } +} diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index 8d8bfaf9b4..4009e564a8 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -16,6 +16,11 @@ # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END========================================================= +app: + ncmp: + avc: + subscription-topic: test-avc-subscription + ncmp: dmi: auth: diff --git a/cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json b/cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json new file mode 100644 index 0000000000..1d84c3a5f2 --- /dev/null +++ b/cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json @@ -0,0 +1,23 @@ +{ + "version": "1.0", + "eventType": "CREATE", + "event": { + "subscription": { + "clientID": "SCO-9989752", + "name": "cm-subscription-001" + }, + "dataType": { + "dataspace": "ALL", + "dataCategory": "CM", + "dataProvider": "CM-SERVICE", + "schemaName": "org.onap.ncmp:cm-network-avc-event.rfc8641", + "schemaVersion": "1.0" + }, + "predicates": { + "datastore": "passthrough-operational", + "datastore-xpath-filter": "//_3gpp-nr-nrm-gnbdufunction:GNBDUFunction/ ", + "_3gpp-nr-nrm-nrcelldu": "NRCellDU" + + } + } +}
\ No newline at end of file diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy index d62f337b75..f6a768a0a1 100644 --- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy +++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy @@ -20,6 +20,7 @@ package org.onap.cps.cpspath.parser +import org.springframework.util.StopWatch import spock.lang.Specification class CpsPathUtilSpec extends Specification { @@ -87,4 +88,18 @@ class CpsPathUtilSpec extends Specification { thrown(PathParsingException) } + def 'CPS Path Processing Performance Test.'() { + when: '200,000 paths are processed' + def setupStopWatch = new StopWatch() + setupStopWatch.start() + (1..100000).each { + CpsPathUtil.getNormalizedXpath('/long/path/to/see/if/it/adds/paring/time/significantly/parent/child[@common-leaf-name="123"]') + CpsPathUtil.getNormalizedXpath('//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent') + } + setupStopWatch.stop() + then: 'it takes less then 10,000 milliseconds' + // In CI this actually takes about 3-5 sec which is approx. 50+ parser executions per millisecond! + assert setupStopWatch.getTotalTimeMillis() < 10000 + } + } diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml index fb0947e54a..4f138fc898 100644 --- a/cps-rest/docs/openapi/components.yml +++ b/cps-rest/docs/openapi/components.yml @@ -211,6 +211,15 @@ components: schema: type: string example: '2021-03-21T00:10:34.030-0100' + apiVersionInPath: + name: apiVersion + in: path + description: apiVersion + required: true + schema: + type: string + enum: [v1, v2] + default: v2 responses: NotFound: @@ -279,6 +288,8 @@ components: schema: type: string example: my-resource + CreatedV2: + description: Created without response body InternalServerError: description: Internal Server Error content: diff --git a/cps-rest/docs/openapi/cpsAdmin.yml b/cps-rest/docs/openapi/cpsAdmin.yml index 595f6d7ec1..f60a9be6ff 100644 --- a/cps-rest/docs/openapi/cpsAdmin.yml +++ b/cps-rest/docs/openapi/cpsAdmin.yml @@ -19,27 +19,6 @@ # ============LICENSE_END========================================================= dataspaces: - post: - description: Create a new dataspace - tags: - - cps-admin - summary: Create a dataspace - operationId: createDataspace - parameters: - - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery' - responses: - '201': - $ref: 'components.yml#/components/responses/Created' - '400': - $ref: 'components.yml#/components/responses/BadRequest' - '401': - $ref: 'components.yml#/components/responses/Unauthorized' - '403': - $ref: 'components.yml#/components/responses/Forbidden' - '409': - $ref: 'components.yml#/components/responses/Conflict' - '500': - $ref: 'components.yml#/components/responses/InternalServerError' delete: description: Delete a dataspace tags: @@ -47,6 +26,7 @@ dataspaces: summary: Delete a dataspace operationId: deleteDataspace parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery' responses: '204': @@ -63,34 +43,6 @@ dataspaces: $ref: 'components.yml#/components/responses/InternalServerError' schemaSet: - post: - description: Create a new schema set in the given dataspace - tags: - - cps-admin - summary: Create a schema set - operationId: createSchemaSet - parameters: - - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' - requestBody: - required: true - content: - multipart/form-data: - schema: - $ref: 'components.yml#/components/schemas/MultipartFile' - responses: - '201': - $ref: 'components.yml#/components/responses/Created' - '400': - $ref: 'components.yml#/components/responses/BadRequest' - '401': - $ref: 'components.yml#/components/responses/Unauthorized' - '403': - $ref: 'components.yml#/components/responses/Forbidden' - '409': - $ref: 'components.yml#/components/responses/Conflict' - '500': - $ref: 'components.yml#/components/responses/InternalServerError' get: description: Read all schema sets, given a dataspace tags: @@ -98,6 +50,7 @@ schemaSet: summary: Get schema sets operationId: getSchemaSets parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' responses: '200': @@ -125,6 +78,7 @@ schemaSetBySchemaSetName: summary: Get a schema set operationId: getSchemaSet parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/schemaSetNameInPath' responses: @@ -149,6 +103,7 @@ schemaSetBySchemaSetName: summary: Delete a schema set operationId: deleteSchemaSet parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/schemaSetNameInPath' responses: @@ -173,6 +128,7 @@ anchorsByDataspace: summary: Get anchors operationId: getAnchors parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' responses: '200': @@ -191,29 +147,6 @@ anchorsByDataspace: $ref: 'components.yml#/components/responses/Forbidden' '500': $ref: 'components.yml#/components/responses/InternalServerError' - post: - description: Create a new anchor in the given dataspace - tags: - - cps-admin - summary: Create an anchor - operationId: createAnchor - parameters: - - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' - - $ref: 'components.yml#/components/parameters/anchorNameInQuery' - responses: - '201': - $ref: 'components.yml#/components/responses/Created' - '400': - $ref: 'components.yml#/components/responses/BadRequest' - '401': - $ref: 'components.yml#/components/responses/Unauthorized' - '403': - $ref: 'components.yml#/components/responses/Forbidden' - '409': - $ref: 'components.yml#/components/responses/Conflict' - '500': - $ref: 'components.yml#/components/responses/InternalServerError' anchorByDataspaceAndAnchorName: get: @@ -223,6 +156,7 @@ anchorByDataspaceAndAnchorName: summary: Get an anchor operationId: getAnchor parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' responses: @@ -247,6 +181,7 @@ anchorByDataspaceAndAnchorName: summary: Delete an anchor operationId: deleteAnchor parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' responses: @@ -268,6 +203,8 @@ adminDataspaces: - cps-admin summary: Get all dataspaces operationId: getAllDataspaces + parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' responses: '200': description: OK @@ -294,6 +231,7 @@ adminDataspace: summary: Get a dataspace operationId: getDataspace parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' responses: '200': diff --git a/cps-rest/docs/openapi/cpsAdminV1Deprecated.yml b/cps-rest/docs/openapi/cpsAdminV1Deprecated.yml new file mode 100644 index 0000000000..56f7f1b4fc --- /dev/null +++ b/cps-rest/docs/openapi/cpsAdminV1Deprecated.yml @@ -0,0 +1,98 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022 TechMahindra Ltd. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +dataspaces: + post: + deprecated: true + description: Create a new dataspace + tags: + - cps-admin + summary: Create a dataspace + operationId: createDataspace + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +anchorsByDataspace: + post: + deprecated: true + description: Create a new anchor in the given dataspace + tags: + - cps-admin + summary: Create an anchor + operationId: createAnchor + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + - $ref: 'components.yml#/components/parameters/anchorNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +schemaSet: + post: + deprecated: true + description: Create a new schema set in the given dataspace + tags: + - cps-admin + summary: Create a schema set + operationId: createSchemaSet + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: 'components.yml#/components/schemas/MultipartFile' + responses: + '201': + $ref: 'components.yml#/components/responses/Created' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsAdminV2.yml b/cps-rest/docs/openapi/cpsAdminV2.yml new file mode 100644 index 0000000000..14e2cfe047 --- /dev/null +++ b/cps-rest/docs/openapi/cpsAdminV2.yml @@ -0,0 +1,95 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022 TechMahindra Ltd. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +dataspaces: + post: + description: Create a new dataspace + tags: + - cps-admin + summary: Create a dataspace + operationId: createDataspaceV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/CreatedV2' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +anchorsByDataspace: + post: + description: Create a new anchor in the given dataspace + tags: + - cps-admin + summary: Create an anchor + operationId: createAnchorV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + - $ref: 'components.yml#/components/parameters/anchorNameInQuery' + responses: + '201': + $ref: 'components.yml#/components/responses/CreatedV2' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' + +schemaSet: + post: + description: Create a new schema set in the given dataspace + tags: + - cps-admin + summary: Create a schema set + operationId: createSchemaSetV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/schemaSetNameInQuery' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: 'components.yml#/components/schemas/MultipartFile' + responses: + '201': + $ref: 'components.yml#/components/responses/CreatedV2' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '409': + $ref: 'components.yml#/components/responses/Conflict' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsData.yml b/cps-rest/docs/openapi/cpsData.yml index 265ee23ad1..9d940c3f83 100644 --- a/cps-rest/docs/openapi/cpsData.yml +++ b/cps-rest/docs/openapi/cpsData.yml @@ -1,6 +1,7 @@ # ============LICENSE_START======================================================= # Copyright (c) 2021-2022 Bell Canada. # Modifications Copyright (C) 2021-2022 Nordix Foundation +# Modifications Copyright (C) 2022 TechMahindra Ltd. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +26,7 @@ nodeByDataspaceAndAnchor: summary: Get a node operationId: getNodeByDataspaceAndAnchor parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' @@ -57,6 +59,7 @@ listElementByDataspaceAndAnchor: summary: Add list element(s) operationId: addListElements parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' @@ -88,6 +91,7 @@ listElementByDataspaceAndAnchor: summary: Replace list content operationId: replaceListContent parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' @@ -112,29 +116,6 @@ listElementByDataspaceAndAnchor: $ref: 'components.yml#/components/responses/Forbidden' '500': $ref: 'components.yml#/components/responses/InternalServerError' - delete: - description: Delete one or all list element(s) for a given anchor and dataspace - deprecated: true - tags: - - cps-data - summary: Delete one or all list element(s) - operationId: deleteListOrListElement - parameters: - - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - - $ref: 'components.yml#/components/parameters/anchorNameInPath' - - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' - - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' - responses: - '204': - $ref: 'components.yml#/components/responses/NoContent' - '400': - $ref: 'components.yml#/components/responses/BadRequest' - '401': - $ref: 'components.yml#/components/responses/Unauthorized' - '403': - $ref: 'components.yml#/components/responses/Forbidden' - '500': - $ref: 'components.yml#/components/responses/InternalServerError' nodesByDataspaceAndAnchor: post: @@ -144,6 +125,7 @@ nodesByDataspaceAndAnchor: summary: Create a node operationId: createNode parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' @@ -177,6 +159,7 @@ nodesByDataspaceAndAnchor: summary: Update node leaves operationId: updateNodeLeaves parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' @@ -208,6 +191,7 @@ nodesByDataspaceAndAnchor: summary: Delete a data node operationId: deleteDataNode parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' @@ -230,6 +214,7 @@ nodesByDataspaceAndAnchor: summary: Replace a node with descendants operationId: replaceNode parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' diff --git a/cps-rest/docs/openapi/cpsDataV1Deprecated.yml b/cps-rest/docs/openapi/cpsDataV1Deprecated.yml new file mode 100644 index 0000000000..194ca3e079 --- /dev/null +++ b/cps-rest/docs/openapi/cpsDataV1Deprecated.yml @@ -0,0 +1,42 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022 TechMahindra Ltd. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +listElementByDataspaceAndAnchor: + delete: + description: Delete one or all list element(s) for a given anchor and dataspace + deprecated: true + tags: + - cps-data + summary: Delete one or all list element(s) + operationId: deleteListOrListElement + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' + - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + responses: + '204': + $ref: 'components.yml#/components/responses/NoContent' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError' diff --git a/cps-rest/docs/openapi/cpsQuery.yml b/cps-rest/docs/openapi/cpsQuery.yml index dc0402d03e..45fc70c861 100644 --- a/cps-rest/docs/openapi/cpsQuery.yml +++ b/cps-rest/docs/openapi/cpsQuery.yml @@ -1,6 +1,7 @@ # ============LICENSE_START======================================================= # Copyright (C) 2021 Nordix Foundation # Modifications Copyright (c) 2022 Bell Canada. +# Modifications Copyright (c) 2022 TechMahindra Ltd. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +26,7 @@ nodesByDataspaceAndAnchorAndCpsPath: summary: Query data nodes operationId: getNodesByDataspaceAndAnchorAndCpsPath parameters: + - $ref: 'components.yml#/components/parameters/apiVersionInPath' - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/cpsPathInQuery' diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml index e02d6a6715..0918b56c3d 100644 --- a/cps-rest/docs/openapi/openapi.yml +++ b/cps-rest/docs/openapi/openapi.yml @@ -51,36 +51,57 @@ tags: paths: /v1/dataspaces: + $ref: 'cpsAdminV1Deprecated.yml#/dataspaces' + + /{apiVersion}/dataspaces: $ref: 'cpsAdmin.yml#/dataspaces' - /v1/admin/dataspaces: + /v2/dataspaces: + $ref: 'cpsAdminV2.yml#/dataspaces' + + /{apiVersion}/admin/dataspaces: $ref: 'cpsAdmin.yml#/adminDataspaces' - /v1/admin/dataspaces/{dataspace-name}: + /{apiVersion}/admin/dataspaces/{dataspace-name}: $ref: 'cpsAdmin.yml#/adminDataspace' /v1/dataspaces/{dataspace-name}/anchors: + $ref: 'cpsAdminV1Deprecated.yml#/anchorsByDataspace' + + /v2/dataspaces/{dataspace-name}/anchors: + $ref: 'cpsAdminV2.yml#/anchorsByDataspace' + + /{apiVersion}/dataspaces/{dataspace-name}/anchors: $ref: 'cpsAdmin.yml#/anchorsByDataspace' - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}: $ref: 'cpsAdmin.yml#/anchorByDataspaceAndAnchorName' /v1/dataspaces/{dataspace-name}/schema-sets: + $ref: 'cpsAdminV1Deprecated.yml#/schemaSet' + + /v2/dataspaces/{dataspace-name}/schema-sets: + $ref: 'cpsAdminV2.yml#/schemaSet' + + /{apiVersion}/dataspaces/{dataspace-name}/schema-sets: $ref: 'cpsAdmin.yml#/schemaSet' - /v1/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: + /{apiVersion}/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: $ref: 'cpsAdmin.yml#/schemaSetBySchemaSetName' - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: $ref: 'cpsData.yml#/nodeByDataspaceAndAnchor' - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: $ref: 'cpsData.yml#/nodesByDataspaceAndAnchor' /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: + $ref: 'cpsDataV1Deprecated.yml#/listElementByDataspaceAndAnchor' + + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: $ref: 'cpsData.yml#/listElementByDataspaceAndAnchor' - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: $ref: 'cpsQuery.yml#/nodesByDataspaceAndAnchorAndCpsPath' security: diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java index 285a15c6a3..b8ba08915c 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java @@ -69,13 +69,25 @@ public class AdminRestController implements CpsAdminApi { } /** + * Create a dataspace without returning any response body. + * + * @param dataspaceName dataspace name + * @return a {@Link ResponseEntity} of created dataspace name & {@link HttpStatus} CREATED + */ + @Override + public ResponseEntity<Void> createDataspaceV2(@NotNull @Valid final String dataspaceName) { + cpsAdminService.createDataspace(dataspaceName); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + /** * Delete a dataspace. * * @param dataspaceName name of dataspace to be deleted * @return a {@Link ResponseEntity} of {@link HttpStatus} NO_CONTENT */ @Override - public ResponseEntity<Void> deleteDataspace(final String dataspaceName) { + public ResponseEntity<Void> deleteDataspace(final String apiVersion, final String dataspaceName) { cpsAdminService.deleteDataspace(dataspaceName); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -96,14 +108,31 @@ public class AdminRestController implements CpsAdminApi { } /** + * Create a {@link SchemaSet}. + * + * @param multipartFile multipart file + * @param schemaSetName schemaset name + * @param dataspaceName dataspace name + * @return a {@Link ResponseEntity} of created schema set without any response body & {@link HttpStatus} CREATED + */ + @Override + public ResponseEntity<Void> createSchemaSetV2(@NotNull @Valid final String schemaSetName, + final String dataspaceName, @Valid final MultipartFile multipartFile) { + cpsModuleService.createSchemaSet(dataspaceName, schemaSetName, extractYangResourcesMap(multipartFile)); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + /** * Get {@link SchemaSetDetails} based on dataspace name & {@link SchemaSet} name. * + * @param apiVersion api version * @param dataspaceName dataspace name * @param schemaSetName schemaset name * @return a {@Link ResponseEntity} of {@Link SchemaSetDetails} & {@link HttpStatus} OK */ @Override - public ResponseEntity<SchemaSetDetails> getSchemaSet(final String dataspaceName, final String schemaSetName) { + public ResponseEntity<SchemaSetDetails> getSchemaSet(final String apiVersion, + final String dataspaceName, final String schemaSetName) { final var schemaSet = cpsModuleService.getSchemaSet(dataspaceName, schemaSetName); final var schemaSetDetails = cpsRestInputMapper.toSchemaSetDetails(schemaSet); return new ResponseEntity<>(schemaSetDetails, HttpStatus.OK); @@ -112,11 +141,12 @@ public class AdminRestController implements CpsAdminApi { /** * Get list of schema sets for a given dataspace name. * + * @param apiVersion api version * @param dataspaceName dataspace name * @return a {@Link ResponseEntity} of schema sets & {@link HttpStatus} OK */ @Override - public ResponseEntity<List<SchemaSetDetails>> getSchemaSets(final String dataspaceName) { + public ResponseEntity<List<SchemaSetDetails>> getSchemaSets(final String apiVersion, final String dataspaceName) { final Collection<SchemaSet> schemaSets = cpsModuleService.getSchemaSets(dataspaceName); final List<SchemaSetDetails> schemaSetDetails = schemaSets.stream().map(cpsRestInputMapper::toSchemaSetDetails) .collect(Collectors.toList()); @@ -126,12 +156,14 @@ public class AdminRestController implements CpsAdminApi { /** * Delete a {@link SchemaSet} based on given dataspace name & schemaset name. * + * @param apiVersion api version * @param dataspaceName dataspace name * @param schemaSetName schemaset name * @return a {@Link ResponseEntity} of {@link HttpStatus} NO_CONTENT */ @Override - public ResponseEntity<Void> deleteSchemaSet(final String dataspaceName, final String schemaSetName) { + public ResponseEntity<Void> deleteSchemaSet(final String apiVersion, + final String dataspaceName, final String schemaSetName) { cpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -152,14 +184,31 @@ public class AdminRestController implements CpsAdminApi { } /** + * Create an anchor. + * + * @param dataspaceName dataspace name + * @param schemaSetName schema set name + * @param anchorName anchorName + * @return a ResponseEntity without response body & {@link HttpStatus} CREATED + */ + @Override + public ResponseEntity<Void> createAnchorV2(final String dataspaceName, @NotNull @Valid final String schemaSetName, + @NotNull @Valid final String anchorName) { + cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + /** * Delete an {@link Anchor} based on given dataspace name & anchor name. * + * @param apiVersion api version * @param dataspaceName dataspace name * @param anchorName anchor name * @return a {@Link ResponseEntity} of {@link HttpStatus} NO_CONTENT */ @Override - public ResponseEntity<Void> deleteAnchor(final String dataspaceName, final String anchorName) { + public ResponseEntity<Void> deleteAnchor(final String apiVersion, + final String dataspaceName, final String anchorName) { cpsAdminService.deleteAnchor(dataspaceName, anchorName); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -167,12 +216,14 @@ public class AdminRestController implements CpsAdminApi { /** * Get an {@link Anchor} based on given dataspace name & anchor name. * + * @param apiVersion api version * @param dataspaceName dataspace name * @param anchorName anchor name * @return a {@Link ResponseEntity} of an {@Link AnchorDetails} & {@link HttpStatus} OK */ @Override - public ResponseEntity<AnchorDetails> getAnchor(final String dataspaceName, final String anchorName) { + public ResponseEntity<AnchorDetails> getAnchor(final String apiVersion, + final String dataspaceName, final String anchorName) { final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); final var anchorDetails = cpsRestInputMapper.toAnchorDetails(anchor); return new ResponseEntity<>(anchorDetails, HttpStatus.OK); @@ -181,11 +232,13 @@ public class AdminRestController implements CpsAdminApi { /** * Get all {@link Anchor} based on given dataspace name. * + * @param apiVersion api version * @param dataspaceName dataspace name * @return a {@Link ResponseEntity} of all {@Link AnchorDetails} & {@link HttpStatus} OK */ @Override - public ResponseEntity<List<AnchorDetails>> getAnchors(final String dataspaceName) { + public ResponseEntity<List<AnchorDetails>> getAnchors(final String apiVersion, + final String dataspaceName) { final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName); final List<AnchorDetails> anchorDetails = anchors.stream().map(cpsRestInputMapper::toAnchorDetails) .collect(Collectors.toList()); @@ -193,7 +246,7 @@ public class AdminRestController implements CpsAdminApi { } @Override - public ResponseEntity<List<DataspaceDetails>> getAllDataspaces() { + public ResponseEntity<List<DataspaceDetails>> getAllDataspaces(final String apiVersion) { final Collection<Dataspace> dataspaces = cpsAdminService.getAllDataspaces(); final List<DataspaceDetails> dataspaceDetails = dataspaces.stream().map(cpsRestInputMapper::toDataspaceDetails) .collect(Collectors.toList()); @@ -201,7 +254,7 @@ public class AdminRestController implements CpsAdminApi { } @Override - public ResponseEntity<DataspaceDetails> getDataspace(final String dataspaceName) { + public ResponseEntity<DataspaceDetails> getDataspace(final String apiVersion, final String dataspaceName) { final Dataspace dataspace = cpsAdminService.getDataspace(dataspaceName); final DataspaceDetails dataspaceDetails = cpsRestInputMapper.toDataspaceDetails(dataspace); return new ResponseEntity<>(dataspaceDetails, HttpStatus.OK); diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java index fdce9bee6b..c7d44b67b3 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java @@ -3,6 +3,7 @@ * Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Nordix Foundation + * Modifications Copyright (C) 2022 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +54,8 @@ public class DataRestController implements CpsDataApi { private final PrefixResolver prefixResolver; @Override - public ResponseEntity<String> createNode(final String dataspaceName, final String anchorName, + public ResponseEntity<String> createNode(final String apiVersion, + final String dataspaceName, final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) { final String jsonDataAsString = jsonObjectMapper.asJsonString(jsonData); if (isRootXpath(parentNodeXpath)) { @@ -67,7 +69,8 @@ public class DataRestController implements CpsDataApi { } @Override - public ResponseEntity<Void> deleteDataNode(final String dataspaceName, final String anchorName, + public ResponseEntity<Void> deleteDataNode(final String apiVersion, + final String dataspaceName, final String anchorName, final String xpath, final String observedTimestamp) { cpsDataService.deleteDataNode(dataspaceName, anchorName, xpath, toOffsetDateTime(observedTimestamp)); @@ -75,7 +78,7 @@ public class DataRestController implements CpsDataApi { } @Override - public ResponseEntity<String> addListElements(final String parentNodeXpath, + public ResponseEntity<String> addListElements(final String parentNodeXpath, final String apiVersion, final String dataspaceName, final String anchorName, final Object jsonData, final String observedTimestamp) { cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp)); @@ -83,8 +86,8 @@ public class DataRestController implements CpsDataApi { } @Override - public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String dataspaceName, final String anchorName, - final String xpath, final Boolean includeDescendants) { + public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String apiVersion, + final String dataspaceName, final String anchorName, final String xpath, final Boolean includeDescendants) { final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS; final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath, @@ -94,7 +97,7 @@ public class DataRestController implements CpsDataApi { } @Override - public ResponseEntity<Object> updateNodeLeaves(final String dataspaceName, + public ResponseEntity<Object> updateNodeLeaves(final String apiVersion, final String dataspaceName, final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) { cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp)); @@ -102,7 +105,8 @@ public class DataRestController implements CpsDataApi { } @Override - public ResponseEntity<Object> replaceNode(final String dataspaceName, final String anchorName, + public ResponseEntity<Object> replaceNode(final String apiVersion, + final String dataspaceName, final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) { cpsDataService .updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, @@ -112,7 +116,7 @@ public class DataRestController implements CpsDataApi { @Override public ResponseEntity<Object> replaceListContent(final String parentNodeXpath, - final String dataspaceName, final String anchorName, final Object jsonData, + final String apiVersion, final String dataspaceName, final String anchorName, final Object jsonData, final String observedTimestamp) { cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp)); diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java index 577ad9c262..3e162ae683 100644 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada. + * Modifications Copyright (C) 2022 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,8 +49,8 @@ public class QueryRestController implements CpsQueryApi { private final PrefixResolver prefixResolver; @Override - public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String dataspaceName, - final String anchorName, final String cpsPath, final Boolean includeDescendants) { + public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String apiVersion, + final String dataspaceName, final String anchorName, final String cpsPath, final Boolean includeDescendants) { final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS; final Collection<DataNode> dataNodes = diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy index 7120ce49f3..f81efd6698 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy @@ -73,21 +73,23 @@ class AdminRestControllerSpec extends Specification { def anchor = new Anchor(name: anchorName, dataspaceName: dataspaceName, schemaSetName: schemaSetName) def dataspace = new Dataspace(name: dataspaceName) - def 'Create new dataspace.'() { - given: 'an endpoint' - def createDataspaceEndpoint = "$basePath/v1/dataspaces" + def 'Create new dataspace with #scenario.'() { when: 'post is invoked' def response = mvc.perform( - post(createDataspaceEndpoint) + post("/cps/api/${apiVersion}/dataspaces") .param('dataspace-name', dataspaceName)) .andReturn().response then: 'service method is invoked with expected parameters' 1 * mockCpsAdminService.createDataspace(dataspaceName) and: 'dataspace is create successfully' response.status == HttpStatus.CREATED.value() - } - + assert response.getContentAsString() == expectedResponseBody + where: 'following cases are tested' + scenario | apiVersion || expectedResponseBody + 'V1 API' | 'v1' || 'my_dataspace' + 'V2 API' | 'v2' || '' + } def 'Create dataspace over existing with same name.'() { given: 'an endpoint' def createDataspaceEndpoint = "$basePath/v1/dataspaces" @@ -129,16 +131,14 @@ class AdminRestControllerSpec extends Specification { response.getContentAsString().contains("dataspace-test2") } - def 'Create schema set from yang file.'() { + def 'Create schema set from yang file with #scenario.'() { def yangResourceMapCapture given: 'single yang file' def multipartFile = createMultipartFile("filename.yang", "content") - and: 'an endpoint' - def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets" when: 'file uploaded with schema set create request' def response = mvc.perform( - multipart(schemaSetEndpoint) + multipart("/cps/api/${apiVersion}/dataspaces/my_dataspace/schema-sets") .file(multipartFile) .param('schema-set-name', schemaSetName)) .andReturn().response @@ -147,19 +147,22 @@ class AdminRestControllerSpec extends Specification { { args -> yangResourceMapCapture = args[2] } yangResourceMapCapture['filename.yang'] == 'content' and: 'response code indicates success' - response.status == HttpStatus.CREATED.value() + assert response.status == HttpStatus.CREATED.value() + assert response.getContentAsString() == expectedResponseBody + where: 'following cases are tested' + scenario | apiVersion || expectedResponseBody + 'V1 API' | 'v1' || 'my_schema_set' + 'V2 API' | 'v2' || '' } - def 'Create schema set from zip archive.'() { + def 'Create schema set from zip archive with #scenario.'() { def yangResourceMapCapture given: 'zip archive with multiple .yang files inside' def multipartFile = createZipMultipartFileFromResource("/yang-files-set.zip") - and: 'an endpoint' - def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets" when: 'file uploaded with schema set create request' def response = mvc.perform( - multipart(schemaSetEndpoint) + multipart("/cps/api/${apiVersion}/dataspaces/my_dataspace/schema-sets") .file(multipartFile) .param('schema-set-name', schemaSetName)) .andReturn().response @@ -169,25 +172,33 @@ class AdminRestControllerSpec extends Specification { yangResourceMapCapture['assembly.yang'] == "fake assembly content 1\n" yangResourceMapCapture['component.yang'] == "fake component content 1\n" and: 'response code indicates success' - response.status == HttpStatus.CREATED.value() + assert response.status == HttpStatus.CREATED.value() + assert response.getContentAsString() == expectedResponseBody + where: 'following cases are tested' + scenario | apiVersion || expectedResponseBody + 'V1 API' | 'v1' || 'my_schema_set' + 'V2 API' | 'v2' || '' } - def 'Create a schema set from a yang file that is greater than 1MB.'() { + def 'Create a schema set from a yang file that is greater than 1MB #scenario.'() { given: 'a yang file greater than 1MB' def multipartFile = createMultipartFileFromResource("/model-over-1mb.yang") - and: 'an endpoint' - def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets" when: 'a file is uploaded to the create schema set endpoint' def response = mvc.perform( - multipart(schemaSetEndpoint) + multipart("/cps/api/${apiVersion}/dataspaces/my_dataspace/schema-sets") .file(multipartFile) .param('schema-set-name', schemaSetName)) .andReturn().response then: 'the associated service method is invoked' 1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) and: 'the response code indicates success' - response.status == HttpStatus.CREATED.value() + assert response.status == HttpStatus.CREATED.value() + assert response.getContentAsString() == expectedResponseBody + where: 'following cases are tested' + scenario | apiVersion || expectedResponseBody + 'V1 API' | 'v1' || 'my_schema_set' + 'V2 API' | 'v2' || '' } def 'Create schema set from zip archive having #caseDescriptor.'() { @@ -293,23 +304,26 @@ class AdminRestControllerSpec extends Specification { '"my_schema_set"},{"dataspaceName":"my_dataspace","moduleReferences":[],"name":"test-schemaset"}]' } - def 'Create Anchor.'() { + def 'Create Anchor with #scenario.'() { given: 'request parameters' def requestParams = new LinkedMultiValueMap<>() requestParams.add('schema-set-name', schemaSetName) requestParams.add('anchor-name', anchorName) - and: 'an endpoint' - def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors" when: 'post is invoked' def response = mvc.perform( - post(anchorEndpoint).contentType(MediaType.APPLICATION_JSON) + post("/cps/api/${apiVersion}/dataspaces/my_dataspace/anchors") + .contentType(MediaType.APPLICATION_JSON) .params(requestParams as MultiValueMap)) - .andReturn().response + .andReturn().response then: 'anchor is created successfully' 1 * mockCpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName) - response.status == HttpStatus.CREATED.value() - response.getContentAsString().contains(anchorName) + assert response.status == HttpStatus.CREATED.value() + assert response.getContentAsString() == expectedResponseBody + where: 'following cases are tested' + scenario | apiVersion || expectedResponseBody + 'V1 API' | 'v1' || 'my_anchor' + 'V2 API' | 'v2' || '' } def 'Get existing anchor.'() { diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java index 27891c525e..6b1162d11b 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java @@ -31,14 +31,13 @@ import lombok.NoArgsConstructor; public class FragmentEntityArranger { /** - * Convert a collection of (related) FragmentExtracts into a FragmentEntity (tree) with descendants. - * Multiple top level nodes not yet support. If found only the first top level element is returned + * Convert a collection of (related) FragmentExtracts into FragmentEntities (trees) with descendants. * * @param anchorEntity the anchor(entity) all the fragments belong to * @param fragmentExtracts FragmentExtracts to convert - * @return a FragmentEntity (tree) with descendants, null if none found. + * @return a collection of FragmentEntities (trees) with descendants. */ - public static FragmentEntity toFragmentEntityTree(final AnchorEntity anchorEntity, + public static Collection<FragmentEntity> toFragmentEntityTrees(final AnchorEntity anchorEntity, final Collection<FragmentExtract> fragmentExtracts) { final Map<Long, FragmentEntity> fragmentEntityPerId = new HashMap<>(); for (final FragmentExtract fragmentExtract : fragmentExtracts) { @@ -61,7 +60,8 @@ public class FragmentEntityArranger { return fragmentEntity; } - private static FragmentEntity reuniteChildrenWithTheirParents(final Map<Long, FragmentEntity> fragmentEntityPerId) { + private static Collection<FragmentEntity> reuniteChildrenWithTheirParents( + final Map<Long, FragmentEntity> fragmentEntityPerId) { final Collection<FragmentEntity> fragmentEntitiesWithoutParentInResultSet = new HashSet<>(); for (final FragmentEntity fragmentEntity : fragmentEntityPerId.values()) { final FragmentEntity parentFragmentEntity = fragmentEntityPerId.get(fragmentEntity.getParentId()); @@ -71,7 +71,7 @@ public class FragmentEntityArranger { parentFragmentEntity.getChildFragments().add(fragmentEntity); } } - return fragmentEntitiesWithoutParentInResultSet.stream().findFirst().orElse(null); + return fragmentEntitiesWithoutParentInResultSet; } } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 82bcea2f1a..3bd2994305 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -61,6 +61,7 @@ import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.repository.AnchorRepository; import org.onap.cps.spi.repository.DataspaceRepository; +import org.onap.cps.spi.repository.FragmentQueryBuilder; import org.onap.cps.spi.repository.FragmentRepository; import org.onap.cps.spi.utils.SessionManager; import org.onap.cps.utils.JsonObjectMapper; @@ -265,36 +266,37 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final String xpath, final FetchDescendantsOption fetchDescendantsOption) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final FragmentEntity fragmentEntity; if (isRootXpath(xpath)) { final List<FragmentExtract> fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity, anchorEntity); - return FragmentEntityArranger.toFragmentEntityTree(anchorEntity, - fragmentExtracts); + fragmentEntity = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts) + .stream().findFirst().orElse(null); } else { final String normalizedXpath = getNormalizedXpath(xpath); - final FragmentEntity fragmentEntity; if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) { fragmentEntity = fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath); } else { - fragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity, normalizedXpath); - } - if (fragmentEntity == null) { - throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath); + fragmentEntity = buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath) + .stream().findFirst().orElse(null); } - return fragmentEntity; } + if (fragmentEntity == null) { + throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath); + } + return fragmentEntity; + } - private FragmentEntity buildFragmentEntityFromFragmentExtracts(final AnchorEntity anchorEntity, - final String normalizedXpath) { - final FragmentEntity fragmentEntity; + private Collection<FragmentEntity> buildFragmentEntitiesFromFragmentExtracts(final AnchorEntity anchorEntity, + final String normalizedXpath) { final List<FragmentExtract> fragmentExtracts = fragmentRepository.findByAnchorIdAndParentXpath(anchorEntity.getId(), normalizedXpath); log.debug("Fetched {} fragment entities by anchor {} and cps path {}.", fragmentExtracts.size(), anchorEntity.getName(), normalizedXpath); - fragmentEntity = FragmentEntityArranger.toFragmentEntityTree(anchorEntity, fragmentExtracts); - return fragmentEntity; + return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + } @Override @@ -308,32 +310,73 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } catch (final PathParsingException e) { throw new CpsPathException(e.getMessage()); } - List<FragmentEntity> fragmentEntities = - fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); + + Collection<FragmentEntity> fragmentEntities; + if (canUseRegexQuickFind(fetchDescendantsOption, cpsPathQuery)) { + return getDataNodesUsingRegexQuickFind(fetchDescendantsOption, anchorEntity, cpsPathQuery); + } + fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { - final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList() - : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + fragmentEntities = getAncestorFragmentEntities(anchorEntity, cpsPathQuery, fragmentEntities); } - return createDataNodesFromFragmentEntities(fetchDescendantsOption, anchorEntity, - fragmentEntities); + return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities); } - private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption, - final AnchorEntity anchorEntity, - final List<FragmentEntity> fragmentEntities) { - final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size()); - for (final FragmentEntity proxiedFragmentEntity : fragmentEntities) { - final DataNode dataNode; + private static boolean canUseRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final CpsPathQuery cpsPathQuery) { + return fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + && !cpsPathQuery.hasLeafConditions() + && !cpsPathQuery.hasTextFunctionCondition(); + } + + private List<DataNode> getDataNodesUsingRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final AnchorEntity anchorEntity, + final CpsPathQuery cpsPathQuery) { + Collection<FragmentEntity> fragmentEntities; + final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true); + final List<FragmentExtract> fragmentExtracts = + fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex); + fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + if (cpsPathQuery.hasAncestorAxis()) { + fragmentEntities = getAncestorFragmentEntities(anchorEntity, cpsPathQuery, fragmentEntities); + } + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); + } + + private Collection<FragmentEntity> getAncestorFragmentEntities(final AnchorEntity anchorEntity, + final CpsPathQuery cpsPathQuery, + Collection<FragmentEntity> fragmentEntities) { + final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); + fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList() + : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + return fragmentEntities; + } + + private List<DataNode> createDataNodesFromProxiedFragmentEntities( + final FetchDescendantsOption fetchDescendantsOption, + final AnchorEntity anchorEntity, + final Collection<FragmentEntity> proxiedFragmentEntities) { + final List<DataNode> dataNodes = new ArrayList<>(proxiedFragmentEntities.size()); + for (final FragmentEntity proxiedFragmentEntity : proxiedFragmentEntities) { if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) { - dataNode = toDataNode(proxiedFragmentEntity, fetchDescendantsOption); + dataNodes.add(toDataNode(proxiedFragmentEntity, fetchDescendantsOption)); } else { final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath()); - final FragmentEntity unproxiedFragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity, - normalizedXpath); - dataNode = toDataNode(unproxiedFragmentEntity, fetchDescendantsOption); + final Collection<FragmentEntity> unproxiedFragmentEntities = + buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath); + for (final FragmentEntity unproxiedFragmentEntity : unproxiedFragmentEntities) { + dataNodes.add(toDataNode(unproxiedFragmentEntity, fetchDescendantsOption)); + } } - dataNodes.add(dataNode); + } + return Collections.unmodifiableList(dataNodes); + } + + private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption, + final Collection<FragmentEntity> fragmentEntities) { + final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size()); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption)); } return Collections.unmodifiableList(dataNodes); } @@ -364,7 +407,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds); } - private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities, + private static Set<String> processAncestorXpath(final Collection<FragmentEntity> fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set<String> ancestorXpath = new HashSet<>(); final Pattern pattern = diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java new file mode 100644 index 0000000000..f107928ca7 --- /dev/null +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java @@ -0,0 +1,139 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.spi.repository; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.cpspath.parser.CpsPathPrefixType; +import org.onap.cps.cpspath.parser.CpsPathQuery; +import org.onap.cps.spi.entities.FragmentEntity; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Slf4j +@Component +public class FragmentQueryBuilder { + private static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/"; + private static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?"; + private static final String REGEX_DESCENDANT_PATH_POSTFIX = "(\\/.*)?"; + private static final String REGEX_END_OF_INPUT = "$"; + + @PersistenceContext + private EntityManager entityManager; + + private final JsonObjectMapper jsonObjectMapper; + + /** + * Create a sql query to retrieve by anchor(id) and cps path. + * + * @param anchorId the id of the anchor + * @param cpsPathQuery the cps path query to be transformed into a sql query + * @return a executable query object + */ + public Query getQueryForAnchorAndCpsPath(final int anchorId, final CpsPathQuery cpsPathQuery) { + final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId"); + final Map<String, Object> queryParameters = new HashMap<>(); + queryParameters.put("anchorId", anchorId); + sqlStringBuilder.append(" AND xpath ~ :xpathRegex"); + final String xpathRegex = getXpathSqlRegex(cpsPathQuery, false); + queryParameters.put("xpathRegex", xpathRegex); + if (cpsPathQuery.hasLeafConditions()) { + sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb"); + queryParameters.put("leafDataAsJson", jsonObjectMapper.asJsonString( + cpsPathQuery.getLeavesData())); + } + + addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); + final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class); + setQueryParameters(query, queryParameters); + return query; + } + + /** + * Create a regular expression (string) for xpath based on the given cps path query. + * + * @param cpsPathQuery the cps path query to determine the required regular expression + * @param includeDescendants include descendants yes or no + * @return a string representing the required regular expression + */ + public static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery, final boolean includeDescendants) { + final StringBuilder xpathRegexBuilder = new StringBuilder(); + if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) { + xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix())); + } else { + xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX); + xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName())); + } + xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX); + if (includeDescendants) { + xpathRegexBuilder.append(REGEX_DESCENDANT_PATH_POSTFIX); + } + xpathRegexBuilder.append(REGEX_END_OF_INPUT); + return xpathRegexBuilder.toString(); + } + + private static String escapeXpath(final String xpath) { + // See https://jira.onap.org/browse/CPS-500 for limitations of this basic escape mechanism + return xpath.replace("[@", "\\[@"); + } + + private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) { + try { + return Integer.parseInt(cpsPathQuery.getTextFunctionConditionValue()); + } catch (final NumberFormatException e) { + return null; + } + } + + private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery, + final StringBuilder sqlStringBuilder, + final Map<String, Object> queryParameters) { + if (cpsPathQuery.hasTextFunctionCondition()) { + sqlStringBuilder.append(" AND ("); + sqlStringBuilder.append("attributes @> jsonb_build_object(:textLeafName, :textValue)"); + sqlStringBuilder + .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValue))"); + queryParameters.put("textLeafName", cpsPathQuery.getTextFunctionConditionLeafName()); + queryParameters.put("textValue", cpsPathQuery.getTextFunctionConditionValue()); + final Integer textValueAsInt = getTextValueAsInt(cpsPathQuery); + if (textValueAsInt != null) { + sqlStringBuilder.append(" OR attributes @> jsonb_build_object(:textLeafName, :textValueAsInt)"); + sqlStringBuilder + .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValueAsInt))"); + queryParameters.put("textValueAsInt", textValueAsInt); + } + sqlStringBuilder.append(")"); + } + } + + private static void setQueryParameters(final Query query, final Map<String, Object> queryParameters) { + for (final Map.Entry<String, Object> queryParameter : queryParameters.entrySet()) { + query.setParameter(queryParameter.getKey(), queryParameter.getValue()); + } + } + +} diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java index 2c25a61a7e..c9461bf062 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java @@ -94,4 +94,12 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, nativeQuery = true)
List<FragmentExtract> findByAnchorIdAndParentXpath(@Param("anchorId") int anchorId,
@Param("parentXpath") String parentXpath);
+
+ @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"
+ + " CAST(attributes AS TEXT) AS attributes"
+ + " FROM FRAGMENT WHERE anchor_id = :anchorId"
+ + " AND xpath ~ :xpathRegex",
+ nativeQuery = true)
+ List<FragmentExtract> quickFindWithDescendants(@Param("anchorId") int anchorId,
+ @Param("xpathRegex") String xpathRegex);
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java index 1d61416cfd..6e8f05f017 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java @@ -20,103 +20,32 @@ package org.onap.cps.spi.repository; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.cpspath.parser.CpsPathPrefixType; import org.onap.cps.cpspath.parser.CpsPathQuery; import org.onap.cps.spi.entities.FragmentEntity; -import org.onap.cps.utils.JsonObjectMapper; @RequiredArgsConstructor @Slf4j public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery { - public static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/"; - public static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$"; - @PersistenceContext private EntityManager entityManager; - private final JsonObjectMapper jsonObjectMapper; + + private final FragmentQueryBuilder fragmentQueryBuilder; @Override @Transactional public List<FragmentEntity> findByAnchorAndCpsPath(final int anchorId, final CpsPathQuery cpsPathQuery) { - final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId"); - final Map<String, Object> queryParameters = new HashMap<>(); - queryParameters.put("anchorId", anchorId); - sqlStringBuilder.append(" AND xpath ~ :xpathRegex"); - final String xpathRegex = getXpathSqlRegex(cpsPathQuery); - queryParameters.put("xpathRegex", xpathRegex); - if (cpsPathQuery.hasLeafConditions()) { - sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb"); - queryParameters.put("leafDataAsJson", jsonObjectMapper.asJsonString( - cpsPathQuery.getLeavesData())); - } - - addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); - final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class); - setQueryParameters(query, queryParameters); + final Query query = fragmentQueryBuilder.getQueryForAnchorAndCpsPath(anchorId, cpsPathQuery); final List<FragmentEntity> fragmentEntities = query.getResultList(); log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size()); return fragmentEntities; } - private static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) { - final StringBuilder xpathRegexBuilder = new StringBuilder(); - if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) { - xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix())); - } else { - xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX); - xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName())); - } - xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX); - return xpathRegexBuilder.toString(); - } - - private static String escapeXpath(final String xpath) { - // See https://jira.onap.org/browse/CPS-500 for limitations of this basic escape mechanism - return xpath.replace("[@", "\\[@"); - } - - private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) { - try { - return Integer.parseInt(cpsPathQuery.getTextFunctionConditionValue()); - } catch (final NumberFormatException e) { - return null; - } - } - - private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder, - final Map<String, Object> queryParameters) { - if (cpsPathQuery.hasTextFunctionCondition()) { - sqlStringBuilder.append(" AND ("); - sqlStringBuilder.append("attributes @> jsonb_build_object(:textLeafName, :textValue)"); - sqlStringBuilder - .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValue))"); - queryParameters.put("textLeafName", cpsPathQuery.getTextFunctionConditionLeafName()); - queryParameters.put("textValue", cpsPathQuery.getTextFunctionConditionValue()); - final Integer textValueAsInt = getTextValueAsInt(cpsPathQuery); - if (textValueAsInt != null) { - sqlStringBuilder.append(" OR attributes @> jsonb_build_object(:textLeafName, :textValueAsInt)"); - sqlStringBuilder - .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValueAsInt))"); - queryParameters.put("textValueAsInt", textValueAsInt); - } - sqlStringBuilder.append(")"); - } - } - - private static void setQueryParameters(final Query query, final Map<String, Object> queryParameters) { - for (final Map.Entry<String, Object> queryParameter : queryParameters.entrySet()) { - query.setParameter(queryParameter.getKey(), queryParameter.getValue()); - } - } - } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy index b26cef4de7..33e83f1013 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy @@ -20,7 +20,7 @@ package org.onap.cps.spi.performance -import org.apache.commons.lang3.time.StopWatch +import org.springframework.util.StopWatch import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.impl.CpsPersistenceSpecBase import org.onap.cps.spi.model.DataNode @@ -56,7 +56,7 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { setupStopWatch.start() createLineage() setupStopWatch.stop() - def setupDurationInMillis = setupStopWatch.getTime() + def setupDurationInMillis = setupStopWatch.getTotalTimeMillis() and: 'setup duration is under #ALLOWED_SETUP_TIME_MS milliseconds' assert setupDurationInMillis < ALLOWED_SETUP_TIME_MS } @@ -66,7 +66,7 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { readStopWatch.start() def result = objectUnderTest.getDataNode('PERF-DATASPACE', 'PERF-ANCHOR', xpath, INCLUDE_ALL_DESCENDANTS) readStopWatch.stop() - def readDurationInMillis = readStopWatch.getTime() + def readDurationInMillis = readStopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS and: 'data node is returned with all the descendants populated' @@ -79,11 +79,10 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { def 'Query parent data node with many descendants by cps-path'() { when: 'query is executed with all descendants' - readStopWatch.reset() readStopWatch.start() def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-parent-1' , INCLUDE_ALL_DESCENDANTS) readStopWatch.stop() - def readDurationInMillis = readStopWatch.getTime() + def readDurationInMillis = readStopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS and: 'data node is returned with all the descendants populated' @@ -92,11 +91,10 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { def 'Query many descendants by cps-path with #scenario'() { when: 'query is executed with all descendants' - readStopWatch.reset() readStopWatch.start() def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-test-grand-child-1', descendantsOption) readStopWatch.stop() - def readDurationInMillis = readStopWatch.getTime() + def readDurationInMillis = readStopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' assert readDurationInMillis < alowedDuration and: 'data node is returned with all the descendants populated' @@ -104,24 +102,24 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { where: 'the following options are used' scenario | descendantsOption || alowedDuration 'omit descendants ' | OMIT_DESCENDANTS || 150 - 'include descendants (although there are none)' | INCLUDE_ALL_DESCENDANTS || 1500 + 'include descendants (although there are none)' | INCLUDE_ALL_DESCENDANTS || 150 } def createLineage() { (1..NUMBER_OF_CHILDREN).each { def childName = "perf-test-child-${it}".toString() - def newChild = goForthAndMultiply(PERF_TEST_PARENT, childName) - objectUnderTest.addChildDataNode('PERF-DATASPACE', 'PERF-ANCHOR', PERF_TEST_PARENT, newChild) + def child = goForthAndMultiply(PERF_TEST_PARENT, childName) + objectUnderTest.addChildDataNode('PERF-DATASPACE', 'PERF-ANCHOR', PERF_TEST_PARENT, child) } } def goForthAndMultiply(parentXpath, childName) { - def children = [] + def grandChildren = [] (1..NUMBER_OF_GRAND_CHILDREN).each { - def child = new DataNodeBuilder().withXpath("${parentXpath}/${childName}/perf-test-grand-child-${it}").build() - children.add(child) + def grandChild = new DataNodeBuilder().withXpath("${parentXpath}/${childName}/perf-test-grand-child-${it}").build() + grandChildren.add(grandChild) } - return new DataNodeBuilder().withXpath("${parentXpath}/${childName}").withChildDataNodes(children).build() + return new DataNodeBuilder().withXpath("${parentXpath}/${childName}").withChildDataNodes(grandChildren).build() } def countDataNodes(dataNodes) { diff --git a/csit/prepare-csit.sh b/csit/prepare-csit.sh index dde961697d..78f0fbde22 100755 --- a/csit/prepare-csit.sh +++ b/csit/prepare-csit.sh @@ -57,7 +57,8 @@ else rm -f ${WORKSPACE}/env.properties cd /tmp git clone "https://gerrit.onap.org/r/ci-management" - source /tmp/ci-management/jjb/integration/include-raw-integration-install-robotframework-py3.sh +# source /tmp/ci-management/jjb/integration/include-raw-integration-install-robotframework-py3.sh + source ${WORKSPACE}/install-robotframework.sh fi # install eteutils diff --git a/csit/pylibs.txt b/csit/pylibs.txt index 4952616540..9fee634156 100644 --- a/csit/pylibs.txt +++ b/csit/pylibs.txt @@ -6,7 +6,7 @@ pyhocon requests robotframework-httplibrary robotframework-requests==0.9.3 -robotframework-selenium2library +robotframework-selenium2library==3.0.0 robotframework-extendedselenium2library robotframework-sshlibrary scapy diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index 09ccbe14a0..ec7d29524e 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -29,6 +29,7 @@ paths: summary: Create a dataspace description: Create a new dataspace operationId: createDataspace + deprecated: true parameters: - name: dataspace-name in: query @@ -95,6 +96,75 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred + /v2/dataspaces: + post: + tags: + - cps-admin + summary: Create a dataspace + description: Create a new dataspace + operationId: createDataspaceV2 + parameters: + - name: dataspace-name + in: query + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + responses: + "201": + description: Created + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + /{apiVersion}/dataspaces: delete: tags: - cps-admin @@ -102,6 +172,7 @@ paths: description: Delete a dataspace operationId: deleteDataspace parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: query description: dataspace-name @@ -163,13 +234,15 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /v1/admin/dataspaces: + /{apiVersion}/admin/dataspaces: get: tags: - cps-admin summary: Get dataspaces description: "Read all dataspaces" operationId: getAllDataspaces + parameters: + - $ref: '#/components/parameters/apiVersionInPath' responses: "200": description: OK @@ -219,7 +292,7 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /v1/admin/dataspaces/{dataspace-name}: + /{apiVersion}/admin/dataspaces/{dataspace-name}: get: tags: - cps-admin @@ -227,6 +300,7 @@ paths: description: Read an dataspace given a dataspace name operationId: getDataspace parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -281,7 +355,7 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /v1/dataspaces/{dataspace-name}/anchors: + /{apiVersion}/dataspaces/{dataspace-name}/anchors: get: tags: - cps-admin @@ -289,6 +363,7 @@ paths: description: "Read all anchors, given a dataspace" operationId: getAnchors parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -345,10 +420,12 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred + /v1/dataspaces/{dataspace-name}/anchors: post: tags: - cps-admin summary: Create an anchor + deprecated: true description: Create a new anchor in the given dataspace operationId: createAnchor parameters: @@ -431,7 +508,89 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}: + /v2/dataspaces/{dataspace-name}/anchors: + post: + tags: + - cps-admin + summary: Create an anchor + description: Create a new anchor in the given dataspace + operationId: createAnchorV2 + parameters: + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: schema-set-name + in: query + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set + - name: anchor-name + in: query + description: anchor-name + required: true + schema: + type: string + example: my-anchor + responses: + "201": + description: Created + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}: get: tags: - cps-admin @@ -439,6 +598,7 @@ paths: description: Read an anchor given an anchor name and a dataspace operationId: getAnchor parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -507,6 +667,7 @@ paths: description: Delete an anchor given an anchor name and a dataspace operationId: deleteAnchor parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -651,6 +812,88 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred + /v2/dataspaces/{dataspace-name}/schema-sets: + post: + tags: + - cps-admin + summary: Create a schema set + description: Create a new schema set in the given dataspace + operationId: createSchemaSetV2 + parameters: + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: schema-set-name + in: query + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/MultipartFile' + required: true + responses: + "201": + description: Created + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + /{apiVersion}/dataspaces/{dataspace-name}/schema-sets: get: tags: - cps-admin @@ -658,13 +901,14 @@ paths: description: "Read schema sets for a given dataspace" operationId: getSchemaSets parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - example: my-dataspace + - $ref: '#/components/parameters/apiVersionInPath' + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace responses: "200": description: OK @@ -714,7 +958,7 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /v1/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: + /{apiVersion}/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: get: tags: - cps-admin @@ -722,6 +966,7 @@ paths: description: Read a schema set given a schema set name and a dataspace operationId: getSchemaSet parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -790,6 +1035,7 @@ paths: description: Delete a schema set given a schema set name and a dataspace operationId: deleteSchemaSet parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -858,7 +1104,7 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: get: tags: - cps-data @@ -867,6 +1113,7 @@ paths: anchor and dataspace operationId: getNodeByDataspaceAndAnchor parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -952,7 +1199,7 @@ paths: message: Internal Server Error details: Internal Server Error occurred x-codegen-request-body-name: xpath - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: put: tags: - cps-data @@ -961,6 +1208,7 @@ paths: \ and a parent node xpath" operationId: replaceNode parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1060,6 +1308,7 @@ paths: description: Create a node for a given anchor and dataspace operationId: createNode parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1168,6 +1417,7 @@ paths: xpath. operationId: deleteDataNode parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1253,6 +1503,7 @@ paths: a parent node xpath operationId: updateNodeLeaves parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1345,7 +1596,7 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: put: tags: - cps-data @@ -1353,6 +1604,7 @@ paths: description: "Replace list content under a given parent, anchor and dataspace" operationId: replaceListContent parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1451,6 +1703,7 @@ paths: description: Add list element(s) to a list for a given anchor and dataspace operationId: addListElements parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1547,6 +1800,7 @@ paths: description: Delete one or all list element(s) for a given anchor and dataspace operationId: deleteListOrListElement parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1624,7 +1878,7 @@ paths: message: Internal Server Error details: Internal Server Error occurred deprecated: true - /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: + /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: get: tags: - cps-query @@ -1632,6 +1886,7 @@ paths: description: Query data nodes for the given dataspace and anchor using CPS path operationId: getNodesByDataspaceAndAnchorAndCpsPath parameters: + - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1718,6 +1973,16 @@ paths: details: Internal Server Error occurred x-codegen-request-body-name: xpath components: + parameters: + apiVersionInPath: + name: apiVersion + in: path + description: apiVersion + required: true + schema: + type: string + enum: [v1, v2] + default: v2 securitySchemes: basicAuth: type: http |