diff options
31 files changed, 904 insertions, 107 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index b5b10b0f74..f7fe46e4e0 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -1,7 +1,7 @@ # ============LICENSE_START=======================================================
# Copyright (C) 2021 Pantheon.tech
# Modifications Copyright (C) 2021-2022 Bell Canada
-# Modifications Copyright (C) 2021-2022 Nordix Foundation
+# Modifications Copyright (C) 2021-2023 Nordix Foundation
# ================================================================================
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -84,16 +84,15 @@ spring: properties:
spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
- spring.json.value.default.type: org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent
spring.json.use.type.headers: false
jackson:
- default-property-inclusion: NON_NULL
- serialization:
- FAIL_ON_EMPTY_BEANS: false
+ default-property-inclusion: NON_NULL
+ serialization:
+ FAIL_ON_EMPTY_BEANS: false
sql:
- init:
- mode: ALWAYS
+ init:
+ mode: ALWAYS
app:
ncmp:
async-m2m:
@@ -104,6 +103,7 @@ app: events:
topic: ${LCM_EVENTS_TOPIC:ncmp-events}
+
notification:
enabled: true
data-updated:
diff --git a/cps-ncmp-events/src/main/resources/schemas/avc-event-schema-v1.json b/cps-ncmp-events/src/main/resources/schemas/avc-event-schema-v1.json new file mode 100644 index 0000000000..6db03f6ebc --- /dev/null +++ b/cps-ncmp-events/src/main/resources/schemas/avc-event-schema-v1.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.events:avc-event-schema:v1", + "$ref": "#/definitions/AvcEvent", + "definitions": { + "AvcEvent": { + "description": "The payload for AVC event.", + "type": "object", + "properties": { + "eventId": { + "description": "The unique id identifying the event generated by DMI for this AVC event.", + "type": "string" + }, + "eventCorrelationId": { + "description": "The request id passed by NCMP for this AVC event.", + "type": "string" + }, + "eventTime": { + "description": "The time of the AVC event. The expected format is 'yyyy-MM-dd'T'HH:mm:ss.SSSZ'.", + "type": "string" + }, + "eventTarget": { + "description": "The target of the AVC event.", + "type": "string" + }, + "eventType": { + "description": "The type of the AVC event.", + "type": "string" + }, + "eventSchema": { + "description": "The event schema for AVC events.", + "type": "string" + }, + "eventSchemaVersion": { + "description": "The event schema version for AVC events.", + "type": "string" + }, + "event": { + "$ref": "#/definitions/Event" + } + }, + "required": [ + "eventId", + "eventCorrelationId", + "eventTime", + "eventTarget", + "eventType", + "eventSchema", + "eventSchemaVersion" + ] + }, + "Event": { + "description": "The AVC event content.", + "type": "object" + } + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java index a9e7164fd7..bc6624dee6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (c) 2022 Nordix Foundation. + * Copyright (c) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,9 @@ public class NcmpAsyncRequestResponseEventConsumer { * * @param dmiAsyncRequestResponseEvent the event to be consumed and produced. */ - @KafkaListener(topics = "${app.ncmp.async-m2m.topic}") + @KafkaListener( + topics = "${app.ncmp.async-m2m.topic}", + properties = {"spring.json.value.default.type=org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent"}) public void consumeAndForward(final DmiAsyncRequestResponseEvent dmiAsyncRequestResponseEvent) { log.debug("Consuming event {} ...", dmiAsyncRequestResponseEvent); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventConsumer.java new file mode 100644 index 0000000000..79a36bf506 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventConsumer.java @@ -0,0 +1,53 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2023 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.notifications.avc; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.event.model.AvcEvent; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +/** + * Listener for AVC events. + */ +@Component +@Slf4j +@RequiredArgsConstructor +@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true) +public class AvcEventConsumer { + + private final AvcEventProducer avcEventProducer; + + /** + * Consume the specified event. + * + * @param avcEvent the event to be consumed and produced. + */ + @KafkaListener( + topics = "dmi-cm-events", + properties = {"spring.json.value.default.type=org.onap.cps.ncmp.event.model.AvcEvent"}) + public void consumeAndForward(final AvcEvent avcEvent) { + log.debug("Consuming AVC event {} ...", avcEvent); + avcEventProducer.sendMessage(avcEvent); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventMapper.java new file mode 100644 index 0000000000..531de46414 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventMapper.java @@ -0,0 +1,50 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.notifications.avc; + +import java.util.UUID; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.onap.cps.ncmp.event.model.AvcEvent; + + +/** + * Mapper for converting incoming {@link AvcEvent} to outgoing {@link AvcEvent}. + */ +@Mapper(componentModel = "spring") +public interface AvcEventMapper { + + @Mapping(source = "eventTime", target = "eventTime") + @Mapping(source = "eventId", target = "eventId", qualifiedByName = "avcEventId") + @Mapping(source = "eventCorrelationId", target = "eventCorrelationId") + @Mapping(source = "eventSchema", target = "eventSchema") + @Mapping(source = "eventSchemaVersion", target = "eventSchemaVersion") + @Mapping(source = "eventTarget", target = "eventTarget") + @Mapping(source = "eventType", target = "eventType") + AvcEvent toOutgoingAvcEvent(AvcEvent incomingAvcEvent); + + @Named("avcEventId") + static String getAvcEventId(String eventId) { + return UUID.randomUUID().toString(); + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventProducer.java new file mode 100644 index 0000000000..049f66100b --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventProducer.java @@ -0,0 +1,52 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.notifications.avc; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.event.model.AvcEvent; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +/** + * Producer for AVC events. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AvcEventProducer { + + private final KafkaTemplate<String, AvcEvent> kafkaTemplate; + + private final AvcEventMapper avcEventMapper; + + /** + * Sends message to the configured topic with a message key. + * + * @param incomingAvcEvent message payload + */ + public void sendMessage(final AvcEvent incomingAvcEvent) { + // generate new event id while keeping other data + final AvcEvent outgoingAvcEvent = avcEventMapper.toOutgoingAvcEvent(incomingAvcEvent); + log.debug("Forwarding AVC event {} to topic {} ", outgoingAvcEvent.getEventId(), "cm-events"); + kafkaTemplate.send("cm-events", outgoingAvcEvent.getEventId(), outgoingAvcEvent); + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventProducerIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventProducerIntegrationSpec.groovy new file mode 100644 index 0000000000..0089f777d3 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/notifications/avc/AvcEventProducerIntegrationSpec.groovy @@ -0,0 +1,83 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2023 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.notifications.avc + +import com.fasterxml.jackson.databind.ObjectMapper +import org.apache.kafka.clients.consumer.KafkaConsumer +import org.mapstruct.factory.Mappers +import org.onap.cps.ncmp.api.impl.async.NcmpAsyncRequestResponseEventMapper +import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec +import org.onap.cps.ncmp.event.model.AvcEvent +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.annotation.DirtiesContext +import org.testcontainers.spock.Testcontainers + +import java.time.Duration + +@SpringBootTest(classes = [AvcEventProducer, AvcEventConsumer, ObjectMapper, JsonObjectMapper]) +@Testcontainers +@DirtiesContext +class AvcEventProducerIntegrationSpec extends MessagingBaseSpec { + + @SpringBean + AvcEventMapper avcEventMapper = Mappers.getMapper(AvcEventMapper.class) + + @SpringBean + AvcEventProducer avcEventProducer = new AvcEventProducer(kafkaTemplate, avcEventMapper) + + @SpringBean + AvcEventConsumer acvEventConsumer = new AvcEventConsumer(avcEventProducer) + + @Autowired + JsonObjectMapper jsonObjectMapper + + def kafkaConsumer = new KafkaConsumer<>(consumerConfigProperties('ncmp-group')) + + def 'Consume and forward valid message'() { + given: 'consumer has a subscription' + kafkaConsumer.subscribe(['cm-events'] as List<String>) + and: 'an event is sent' + def jsonData = TestUtils.getResourceFileContent('sampleAvcInputEvent.json') + def testEventSent = jsonObjectMapper.convertJsonString(jsonData, AvcEvent.class) + when: 'the event is consumed' + acvEventConsumer.consumeAndForward(testEventSent) + and: 'the topic is polled' + def records = kafkaConsumer.poll(Duration.ofMillis(1500)) + then: 'poll returns one record' + assert records.size() == 1 + and: 'record can be converted to AVC event' + def record = records.iterator().next() + def convertedAvcEvent = jsonObjectMapper.convertJsonString(record.value(), AvcEvent) + and: 'consumed forwarded NCMP event id differs from DMI event id' + assert testEventSent.eventId != convertedAvcEvent.getEventId() + and: 'correlation id matches' + assert testEventSent.eventCorrelationId == convertedAvcEvent.getEventCorrelationId() + and: 'timestamps match' + assert testEventSent.eventTime == convertedAvcEvent.getEventTime() + and: 'target matches' + assert testEventSent.eventTarget == convertedAvcEvent.getEventTarget() + } + +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy index f7c41ecdf2..bb0ce8745d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (c) 2022 Nordix Foundation. + * Copyright (c) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,14 +33,14 @@ import spock.lang.Specification class MessagingBaseSpec extends Specification { - static { - Runtime.getRuntime().addShutdownHook(new Thread(kafkaTestContainer::stop)) - } - def setupSpec() { kafkaTestContainer.start() } + def cleanupSpec() { + kafkaTestContainer.stop() + } + static kafkaTestContainer = new KafkaContainer(DockerImageName.parse('registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1').asCompatibleSubstituteFor('confluentinc/cp-kafka')) def producerConfigProperties() { diff --git a/cps-ncmp-service/src/test/resources/sampleAvcInputEvent.json b/cps-ncmp-service/src/test/resources/sampleAvcInputEvent.json new file mode 100644 index 0000000000..d7d252b9aa --- /dev/null +++ b/cps-ncmp-service/src/test/resources/sampleAvcInputEvent.json @@ -0,0 +1,12 @@ +{ + "eventId": "4cb32729-85e3-44d1-aa6e-c923b9b059a5", + "eventCorrelationId": "68f15800-8ed4-4bae-9e53-27a9e03e1911", + "eventTime": "2022-12-12T14:29:23.876+0000", + "eventTarget": "NCMP", + "eventType": "org.onap.cps.ncmp.event.model.AvcEvent", + "eventSchema": "urn:cps:org.onap.cps.ncmp.event.model.AvcEvent", + "eventSchemaVersion": "v1", + "event": { + "payload": "Hello world!" + } +}
\ No newline at end of file diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy index e5e304b47d..1dfd194c82 100644 --- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy +++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/performance/CpsPathUtilPerfTest.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,9 +35,8 @@ class CpsPathUtilPerfTest extends Specification { CpsPathUtil.getNormalizedXpath('//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent') } stopWatch.stop() - then: 'it takes less then 1,100 milliseconds' - // In CI this actually takes about 0.3-0.5 sec which is approx. 50+ parser executions per millisecond! - assert stopWatch.getTotalTimeMillis() < 1100 + then: 'it takes less then 2100 milliseconds' + assert stopWatch.getTotalTimeMillis() < 2100 } } diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml index e700da6ea1..60b4ca3189 100644 --- a/cps-rest/docs/openapi/components.yml +++ b/cps-rest/docs/openapi/components.yml @@ -1,7 +1,7 @@ # ============LICENSE_START======================================================= # Copyright (c) 2021-2022 Bell Canada. # Modifications Copyright (C) 2021-2022 Nordix Foundation -# Modifications Copyright (C) 2022 TechMahindra Ltd. +# Modifications Copyright (C) 2022-2023 TechMahindra Ltd. # Modifications Copyright (C) 2022 Deutsche Telekom AG # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); @@ -240,6 +240,15 @@ components: type: string example: 'application/json' required: true + descendantsInQuery: + name: descendants + in: query + description: descendents to query depth of children. allowed values are none, all, any number starting from -1 + required: false + schema: + type: string + default: none + example: 3 responses: NotFound: diff --git a/cps-rest/docs/openapi/cpsData.yml b/cps-rest/docs/openapi/cpsData.yml index 0dc388706c..1d60e1f780 100644 --- a/cps-rest/docs/openapi/cpsData.yml +++ b/cps-rest/docs/openapi/cpsData.yml @@ -1,7 +1,7 @@ # ============LICENSE_START======================================================= # Copyright (c) 2021-2022 Bell Canada. # Modifications Copyright (C) 2021-2022 Nordix Foundation -# Modifications Copyright (C) 2022 TechMahindra Ltd. +# Modifications Copyright (C) 2022-2023 TechMahindra Ltd. # Modifications Copyright (C) 2022 Deutsche Telekom AG # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,39 +19,6 @@ # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END========================================================= -nodeByDataspaceAndAnchor: - get: - description: Get a node with an option to retrieve all the children for a given anchor and dataspace - tags: - - cps-data - 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' - - $ref: 'components.yml#/components/parameters/includeDescendantsOptionInQuery' - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - examples: - dataSample: - $ref: 'components.yml#/components/examples/dataSample' - '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' - x-codegen-request-body-name: xpath - listElementByDataspaceAndAnchor: post: description: Add list element(s) to a list for a given anchor and dataspace diff --git a/cps-rest/docs/openapi/cpsDataV1Deprecated.yml b/cps-rest/docs/openapi/cpsDataV1Deprecated.yml index 194ca3e079..67ddecd2a4 100644 --- a/cps-rest/docs/openapi/cpsDataV1Deprecated.yml +++ b/cps-rest/docs/openapi/cpsDataV1Deprecated.yml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2022 TechMahindra Ltd. +# Copyright (C) 2022-2023 TechMahindra Ltd. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +16,39 @@ # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END========================================================= +nodeByDataspaceAndAnchor: + get: + description: Get a node with an option to retrieve all the children for a given anchor and dataspace + deprecated: true + tags: + - cps-data + summary: Get a node + operationId: getNodeByDataspaceAndAnchor + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/includeDescendantsOptionInQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + '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' + x-codegen-request-body-name: xpath + listElementByDataspaceAndAnchor: delete: description: Delete one or all list element(s) for a given anchor and dataspace diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml new file mode 100644 index 0000000000..61663ab3a8 --- /dev/null +++ b/cps-rest/docs/openapi/cpsDataV2.yml @@ -0,0 +1,49 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2022-2023 TechMahindra Ltd. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +nodeByDataspaceAndAnchor: + get: + description: Get a node with an option to retrieve all the children for a given anchor and dataspace + tags: + - cps-data + summary: Get a node + operationId: getNodeByDataspaceAndAnchorV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/descendantsInQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + '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' + x-codegen-request-body-name: xpath diff --git a/cps-rest/docs/openapi/cpsQuery.yml b/cps-rest/docs/openapi/cpsQueryV1Deprecated.yml index 45fc70c861..6ec117f313 100644 --- a/cps-rest/docs/openapi/cpsQuery.yml +++ b/cps-rest/docs/openapi/cpsQueryV1Deprecated.yml @@ -1,7 +1,7 @@ # ============LICENSE_START======================================================= # Copyright (C) 2021 Nordix Foundation # Modifications Copyright (c) 2022 Bell Canada. -# Modifications Copyright (c) 2022 TechMahindra Ltd. +# Modifications Copyright (c) 2023 TechMahindra Ltd. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ nodesByDataspaceAndAnchorAndCpsPath: tags: - cps-query summary: Query data nodes + deprecated: true 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/cpsQueryV2.yml b/cps-rest/docs/openapi/cpsQueryV2.yml new file mode 100644 index 0000000000..5bfd1bbd23 --- /dev/null +++ b/cps-rest/docs/openapi/cpsQueryV2.yml @@ -0,0 +1,49 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2023 TechMahindra Ltd. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +nodesByDataspaceAndAnchorAndCpsPath: + get: + description: Query data nodes for the given dataspace and anchor using CPS path + tags: + - cps-query + summary: Query data nodes + operationId: getNodesByDataspaceAndAnchorAndCpsPathV2 + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/cpsPathInQuery' + - $ref: 'components.yml#/components/parameters/descendantsInQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSample' + '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' + x-codegen-request-body-name: xpath diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml index 0918b56c3d..0ac825a6dc 100644 --- a/cps-rest/docs/openapi/openapi.yml +++ b/cps-rest/docs/openapi/openapi.yml @@ -2,7 +2,7 @@ # Copyright (C) 2021 Nordix Foundation # Modifications Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2021 Bell Canada. -# Modifications Copyright (C) 2022 TechMahindra Ltd. +# Modifications Copyright (C) 2022-2023 TechMahindra Ltd. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -89,8 +89,11 @@ paths: /{apiVersion}/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: $ref: 'cpsAdmin.yml#/schemaSetBySchemaSetName' - /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: - $ref: 'cpsData.yml#/nodeByDataspaceAndAnchor' + /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + $ref: 'cpsDataV1Deprecated.yml#/nodeByDataspaceAndAnchor' + + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + $ref: 'cpsDataV2.yml#/nodeByDataspaceAndAnchor' /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: $ref: 'cpsData.yml#/nodesByDataspaceAndAnchor' @@ -101,8 +104,11 @@ paths: /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: $ref: 'cpsData.yml#/listElementByDataspaceAndAnchor' - /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: - $ref: 'cpsQuery.yml#/nodesByDataspaceAndAnchorAndCpsPath' + /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: + $ref: 'cpsQueryV1Deprecated.yml#/nodesByDataspaceAndAnchorAndCpsPath' + + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: + $ref: 'cpsQueryV2.yml#/nodesByDataspaceAndAnchorAndCpsPath' security: - basicAuth: [] 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 30bed12775..3a9c764bc6 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,7 +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. + * Modifications Copyright (C) 2023 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -93,8 +93,8 @@ public class DataRestController implements CpsDataApi { } @Override - public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String apiVersion, - final String dataspaceName, final String anchorName, final String xpath, final Boolean includeDescendants) { + public ResponseEntity<Object> getNodeByDataspaceAndAnchor(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, @@ -104,6 +104,17 @@ public class DataRestController implements CpsDataApi { } @Override + public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName, + final String xpath, final String fetchDescendantsOptionAsString) { + final FetchDescendantsOption fetchDescendantsOption = + FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); + final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath, + fetchDescendantsOption); + final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath); + return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK); + } + + @Override 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, @@ -151,4 +162,5 @@ public class DataRestController implements CpsDataApi { String.format("observed-timestamp must be in '%s' format", ISO_TIMESTAMP_FORMAT)); } } + } 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 3e162ae683..81938dcd95 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,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada. - * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,22 +49,36 @@ public class QueryRestController implements CpsQueryApi { private final PrefixResolver prefixResolver; @Override - public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String apiVersion, - final String dataspaceName, final String anchorName, final String cpsPath, final Boolean includeDescendants) { + public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(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; + return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath, + fetchDescendantsOption); + } + + @Override + public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPathV2(final String dataspaceName, + final String anchorName, final String cpsPath, final String fetchDescendantsOptionAsString) { + final FetchDescendantsOption fetchDescendantsOption = + FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); + return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath, + fetchDescendantsOption); + } + + private ResponseEntity<Object> executeNodesByDataspaceQueryAndCreateResponse(final String dataspaceName, + final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { final Collection<DataNode> dataNodes = cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption); - final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size()); + final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size()); String prefix = null; for (final DataNode dataNode : dataNodes) { if (prefix == null) { prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath()); } final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix); - dataMaps.add(dataMap); + dataNodesAsListOfMaps.add(dataMap); } - - return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK); + return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataNodesAsListOfMaps), HttpStatus.OK); } } diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy index 94f62f8c22..16d106ba60 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy @@ -4,6 +4,7 @@ * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2022 Deutsche Telekom AG + * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,6 +69,7 @@ class DataRestControllerSpec extends Specification { def basePath def dataNodeBaseEndpoint + def dataNodeBaseEndpointV2 def dataspaceName = 'my_dataspace' def anchorName = 'my_anchor' def noTimestamp = null @@ -94,6 +96,7 @@ class DataRestControllerSpec extends Specification { def setup() { dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName" + dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName" } def 'Create a node: #scenario.'() { @@ -237,6 +240,28 @@ class DataRestControllerSpec extends Specification { 'with descendants' | dataNodeWithChild | 'true' || INCLUDE_ALL_DESCENDANTS | true | 'parent' } + + def 'Get data node using v2 api'() { + given: 'the service returns data node' + def xpath = 'some xPath' + def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" + mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, {descendantsOption -> { + assert descendantsOption.depth == 2}}) >> dataNodeWithChild + when: 'get request is performed through REST API' + def response = + mvc.perform( + get(endpoint) + .param('xpath', xpath) + .param('descendants', '2')) + .andReturn().response + then: 'a success response is returned' + assert response.status == HttpStatus.OK.value() + and: 'the response contains the root node identifier' + assert response.contentAsString.contains('parent') + and: 'the response contains child is true' + assert response.contentAsString.contains('"child"') == true + } + def 'Update data node leaves: #scenario.'() { given: 'endpoint to update a node ' def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy index 27ca0cc097..b881c3832a 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy @@ -3,6 +3,7 @@ * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,13 +59,19 @@ class QueryRestControllerSpec extends Specification { @Value('${rest.api.cps-base-path}') def basePath + def dataspaceName = 'my_dataspace' + def anchorName = 'my_anchor' + def cpsPath = 'some cps-path' + def dataNodeEndpointV2 + + def setup() { + dataNodeEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query" + } + def 'Query data node by cps path for the given dataspace and anchor with #scenario.'() { given: 'service method returns a list containing a data node' def dataNode1 = new DataNodeBuilder().withXpath('/xpath') .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - def dataspaceName = 'my_dataspace' - def anchorName = 'my_anchor' - def cpsPath = 'some cps-path' mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, expectedCpsDataServiceOption) >> [dataNode1, dataNode1] and: 'the query endpoint' def dataNodeEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query" @@ -84,4 +91,23 @@ class QueryRestControllerSpec extends Specification { 'no descendant explicitly' | 'false' || OMIT_DESCENDANTS 'descendants' | 'true' || INCLUDE_ALL_DESCENDANTS } + + def 'Query data node v2 api by cps path for the given dataspace and anchor with #scenario.'() { + given: 'service method returns a list containing a data node' + def dataNode1 = new DataNodeBuilder().withXpath('/xpath') + .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() + mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption -> { + assert descendantsOption.depth == 2}}) >> [dataNode1, dataNode1] + when: 'query data nodes API is invoked' + def response = + mvc.perform( + get(dataNodeEndpointV2) + .param('cps-path', cpsPath) + .param('descendants', '2')) + .andReturn().response + then: 'the response contains the the datanode in json format' + assert response.status == HttpStatus.OK.value() + assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') + } + } 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 5b0683e979..06ee8ecada 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 @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2022 TechMahindra Ltd. @@ -262,13 +262,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final FetchDescendantsOption fetchDescendantsOption) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); - final List<FragmentEntity> fragmentEntities = - fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorEntity.getId(), xpaths); - final Collection<DataNode> dataNodesCollection = new ArrayList<>(fragmentEntities.size()); - for (final FragmentEntity fragmentEntity : fragmentEntities) { - dataNodesCollection.add(toDataNode(fragmentEntity, fetchDescendantsOption)); + + final Set<String> normalizedXpaths = new HashSet<>(xpaths.size()); + for (final String xpath : xpaths) { + try { + normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath)); + } catch (final PathParsingException e) { + log.warn("Error parsing xpath \"{}\" in getDataNodes: {}", xpath, e.getMessage()); + } } - return dataNodesCollection; + + final List<FragmentEntity> fragmentEntities = + fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorEntity.getId(), normalizedXpaths); + return toDataNodes(fragmentEntities, fetchDescendantsOption); } private FragmentEntity getFragmentWithoutDescendantsByXpath(final String dataspaceName, @@ -449,6 +455,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService .withChildDataNodes(childDataNodes).build(); } + private Collection<DataNode> toDataNodes(final Collection<FragmentEntity> fragmentEntities, + final FetchDescendantsOption fetchDescendantsOption) { + final Collection<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size()); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption)); + } + return dataNodes; + } + private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { if (fetchDescendantsOption.hasNext()) { diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java index b936e5c762..8c357bbb31 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation. + * Copyright (C) 2022-2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ package org.onap.cps.spi.repository; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import javax.persistence.EntityManager; @@ -46,6 +47,9 @@ public class FragmentRepositoryMultiPathQueryImpl implements FragmentRepositoryM @Transactional public List<FragmentEntity> findByAnchorAndMultipleCpsPaths(final Integer anchorId, final Collection<String> cpsPathQueryList) { + if (cpsPathQueryList.isEmpty()) { + return Collections.emptyList(); + } final Collection<List<String>> sqlData = new HashSet<>(cpsPathQueryList.size()); for (final String query : cpsPathQueryList) { final List<String> row = new ArrayList<>(1); diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy index b6d2c5d65e..ba8425fef5 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy @@ -3,6 +3,7 @@ * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021 Bell Canada. + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +23,7 @@ package org.onap.cps.spi.impl import org.onap.cps.spi.CpsDataPersistenceService +import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.CpsPathException import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql @@ -41,7 +43,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SET_DATA]) def 'Cps Path query for leaf value(s) with : #scenario.'() { when: 'a query is executed to get a data node by the given cps path' - def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, includeDescendantsOption) + def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, fetchDescendantsOption) then: 'the correct number of parent nodes are returned' result.size() == expectedNumberOfParentNodes then: 'the correct data is returned' @@ -49,10 +51,12 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { assert it.getChildDataNodes().size() == expectedNumberOfChildNodes } where: 'the following data is used' - scenario | cpsPath | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes - 'String and no descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS || 1 | 0 - 'Integer and descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]' | INCLUDE_ALL_DESCENDANTS || 1 | 1 - 'No condition no descendants' | '/shops/shop[@id=1]/categories' | OMIT_DESCENDANTS || 3 | 0 + scenario | cpsPath | fetchDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes + 'String and no descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS || 1 | 0 + 'Integer and descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]' | INCLUDE_ALL_DESCENDANTS || 1 | 1 + 'No condition no descendants' | '/shops/shop[@id=1]/categories' | OMIT_DESCENDANTS || 3 | 0 + 'Integer and level 1 descendants' | '/shops' | new FetchDescendantsOption(1) || 1 | 5 + 'Integer and level 2 descendants' | '/shops/shop[@id=1]' | new FetchDescendantsOption(2) || 1 | 3 } @Sql([CLEAR_DATA, SET_DATA]) diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy index cc2369d50e..6252fff56c 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2022 TechMahindra Ltd. @@ -296,6 +296,39 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) + def 'Get multiple data nodes by xpath.'() { + when: 'fetch #scenario.' + def results = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, inputXpaths, OMIT_DESCENDANTS) + then: 'the expected number of data nodes are returned' + assert results.size() == expectedResultSize + where: 'following parameters were used' + scenario | inputXpaths || expectedResultSize + '1 node' | ["/parent-200"] || 1 + '2 unique nodes' | ["/parent-200", "/parent-201"] || 2 + '3 unique nodes' | ["/parent-200", "/parent-201", "/parent-202"] || 3 + '1 unique node with duplicate xpath' | ["/parent-200", "/parent-200"] || 1 + '2 unique nodes with duplicate xpath' | ["/parent-200", "/parent-202", "/parent-200"] || 2 + 'list element with key (single quote)' | ["/parent-201/child-204[@key='A']"] || 1 + 'list element with key (double quote)' | ['/parent-201/child-204[@key="A"]'] || 1 + 'non-existing xpath' | ["/NO-XPATH"] || 0 + 'existing and non-existing xpaths' | ["/parent-200", "/NO-XPATH", "/parent-201"] || 2 + 'invalid xpath' | ["INVALID XPATH"] || 0 + 'valid and invalid xpaths' | ["/parent-200", "INVALID XPATH", "/parent-201"] || 2 + } + + @Sql([CLEAR_DATA, SET_DATA]) + def 'Get multiple data nodes error scenario: #scenario.'() { + when: 'attempt to get data nodes with #scenario' + objectUnderTest.getDataNodes(dataspaceName, anchorName, ['/not-relevant'], OMIT_DESCENDANTS) + then: 'a #expectedException is thrown' + thrown(expectedException) + where: 'the following data is used' + scenario | dataspaceName | anchorName || expectedException + 'non-existing dataspace' | 'NO DATASPACE' | 'not relevant' || DataspaceNotFoundException + 'non-existing anchor' | DATASPACE_NAME | 'NO ANCHOR' || AnchorNotFoundException + } + + @Sql([CLEAR_DATA, SET_DATA]) def 'Update data node leaves.'() { when: 'update is performed for leaves' objectUnderTest.updateDataLeaves(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index 01c1dc70ec..87e59c60dc 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -1,8 +1,8 @@ /* * ============LICENSE_START======================================================= * Copyright (c) 2021 Bell Canada. - * Modifications Copyright (C) 2021-2022 Nordix Foundation - * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications Copyright (C) 2021-2023 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. @@ -146,11 +146,11 @@ class CpsDataPersistenceServiceSpec extends Specification { def anchorEntity = new AnchorEntity(id:123) mockAnchorRepository.getByDataspaceAndName(*_) >> anchorEntity and: 'fragment repository returns a collection of fragments' - def fragmentEntity1 = new FragmentEntity(xpath: 'xpath1', childFragments: []) - def fragmentEntity2 = new FragmentEntity(xpath: 'xpath2', childFragments: []) - mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['xpath1','xpath2']) >> [ fragmentEntity1, fragmentEntity2 ] + def fragmentEntity1 = new FragmentEntity(xpath: '/xpath1', childFragments: []) + def fragmentEntity2 = new FragmentEntity(xpath: '/xpath2', childFragments: []) + mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/xpath1', '/xpath2'] as Set<String>) >> [fragmentEntity1, fragmentEntity2] when: 'getting data nodes for 2 xpaths' - def result = objectUnderTest.getDataNodes('some-dataspace', 'some-anchor', ['xpath1','xpath2'],FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + def result = objectUnderTest.getDataNodes('some-dataspace', 'some-anchor', ['/xpath1', '/xpath2'], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) then: '2 data nodes are returned' assert result.size() == 2 } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy index 28363d74b5..2346239dff 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy @@ -51,8 +51,6 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase { static def NUMBER_OF_CHILDREN = 200 static def NUMBER_OF_GRAND_CHILDREN = 50 static def TOTAL_NUMBER_OF_NODES = 1 + NUMBER_OF_CHILDREN + (NUMBER_OF_CHILDREN * NUMBER_OF_GRAND_CHILDREN) // Parent + Children + Grand-children - static def ALLOWED_SETUP_TIME_MS = TimeUnit.SECONDS.toMillis(10) - static def ALLOWED_READ_TIME_AL_NODES_MS = 500 def stopWatch = new StopWatch() def readStopWatch = new StopWatch() @@ -64,8 +62,8 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase { createLineage(objectUnderTest, NUMBER_OF_CHILDREN, NUMBER_OF_GRAND_CHILDREN, false) stopWatch.stop() def setupDurationInMillis = stopWatch.getTotalTimeMillis() - and: 'setup duration is under #ALLOWED_SETUP_TIME_MS milliseconds' - assert setupDurationInMillis < ALLOWED_SETUP_TIME_MS + and: 'setup duration is under 10 seconds' + assert setupDurationInMillis < 10000 } def 'Get data node with many descendants by xpath #scenario'() { @@ -75,7 +73,7 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase { stopWatch.stop() def readDurationInMillis = stopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' - assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS + assert readDurationInMillis < 500 and: 'data node is returned with all the descendants populated' assert countDataNodes(result) == TOTAL_NUMBER_OF_NODES where: 'the following xPaths are used' @@ -91,7 +89,7 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase { stopWatch.stop() def readDurationInMillis = stopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' - assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS + assert readDurationInMillis < 500 and: 'data node is returned with all the descendants populated' assert countDataNodes(result) == TOTAL_NUMBER_OF_NODES } @@ -115,12 +113,12 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase { def result = objectUnderTest.queryDataNodes(PERF_DATASPACE, PERF_ANCHOR, '//perf-test-grand-child-1', descendantsOption) stopWatch.stop() def readDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'read duration is under 500 milliseconds' - assert readDurationInMillis < alowedDuration + then: 'read duration is under #allowedDuration milliseconds' + assert readDurationInMillis < allowedDuration and: 'data node is returned with all the descendants populated' assert result.size() == NUMBER_OF_CHILDREN where: 'the following options are used' - scenario | descendantsOption || alowedDuration + scenario | descendantsOption || allowedDuration 'omit descendants ' | OMIT_DESCENDANTS || 150 'include descendants (although there are none)' | INCLUDE_ALL_DESCENDANTS || 150 } diff --git a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java index b80054ac3b..0c8cddcd73 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java +++ b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech * Copyright (C) 2022 Nordix Foundation + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +21,11 @@ package org.onap.cps.spi; +import com.google.common.base.Strings; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; +import org.onap.cps.spi.exceptions.DataValidationException; @RequiredArgsConstructor public class FetchDescendantsOption { @@ -29,6 +34,9 @@ public class FetchDescendantsOption { public static final FetchDescendantsOption OMIT_DESCENDANTS = new FetchDescendantsOption(0); public static final FetchDescendantsOption INCLUDE_ALL_DESCENDANTS = new FetchDescendantsOption(-1); + private static final Pattern FETCH_DESCENDANTS_OPTION_PATTERN = + Pattern.compile("^$|^all$|^none$|^[0-9]+$|^-1$"); + private final int depth; /** @@ -58,6 +66,36 @@ public class FetchDescendantsOption { return nextDescendantsOption; } + /** + * get fetch descendants option for given descendant. + * + * @param fetchDescendantsOptionAsString fetch descendants option string + * @return fetch descendants option for given descendant + */ + public static FetchDescendantsOption getFetchDescendantsOption(final String fetchDescendantsOptionAsString) { + validateFetchDescendantsOption(fetchDescendantsOptionAsString); + if (Strings.isNullOrEmpty(fetchDescendantsOptionAsString) + || "0".equals(fetchDescendantsOptionAsString) || "none".equals(fetchDescendantsOptionAsString)) { + return FetchDescendantsOption.OMIT_DESCENDANTS; + } else if ("-1".equals(fetchDescendantsOptionAsString) || "all".equals(fetchDescendantsOptionAsString)) { + return FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; + } else { + final Integer depth = Integer.valueOf(fetchDescendantsOptionAsString); + return new FetchDescendantsOption(depth); + } + } + + private static void validateFetchDescendantsOption(final String fetchDescendantsOptionAsString) { + if (Strings.isNullOrEmpty(fetchDescendantsOptionAsString)) { + return; + } + final Matcher matcher = FETCH_DESCENDANTS_OPTION_PATTERN.matcher(fetchDescendantsOptionAsString); + if (!matcher.matches()) { + throw new DataValidationException("FetchDescendantsOption validation error.", + fetchDescendantsOptionAsString + " is not valid fetch descendants option"); + } + } + private static void validateDepth(final int depth) { if (depth < -1) { throw new IllegalArgumentException("A depth of less than minus one is not allowed"); diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy index 8b232b420d..60286b6643 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Nordix Foundation + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +44,8 @@ class CpsQueryServiceImplSpec extends Specification { and: 'the CpsValidator is called on the dataspaceName, schemaSetName and anchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) where: 'all fetch descendants options are supported' - fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS] + fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS, + FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)] } } diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy index 627383561a..c4d3dd8b7b 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +21,7 @@ package org.onap.cps.spi - +import org.onap.cps.spi.exceptions.DataValidationException import spock.lang.Specification class FetchDescendantsOptionSpec extends Specification { @@ -72,4 +73,18 @@ class FetchDescendantsOptionSpec extends Specification { then: 'exception thrown' thrown IllegalArgumentException } + + def 'Create fetch descendant option with descendant using #scenario'() { + when: 'the next level of depth is not allowed' + def FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString) + then: 'fetch descendant object created' + assert fetchDescendantsOption.depth == expectedDepth + where: 'following parameters are used' + scenario | fetchDescendantsOptionAsString || expectedDepth + 'all descendants using number' | '-1' || -1 + 'all descendants using all' | 'all' || -1 + 'No descendants by default' | '' || 0 + 'No descendants using none' | 'none' || 0 + 'til 10th descendants using number' | '10' || 10 + } } diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index ec7d29524e..80766cc070 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -1104,16 +1104,16 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred - /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: get: tags: - cps-data summary: Get a node description: Get a node with an option to retrieve all the children for a given anchor and dataspace + deprecated: true operationId: getNodeByDataspaceAndAnchor parameters: - - $ref: '#/components/parameters/apiVersionInPath' - name: dataspace-name in: path description: dataspace-name @@ -1199,6 +1199,101 @@ paths: message: Internal Server Error details: Internal Server Error occurred x-codegen-request-body-name: xpath + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: + get: + tags: + - cps-data + summary: Get a node + description: Get a node with an option to retrieve all the children for a given + anchor and dataspace + operationId: getNodeByDataspaceAndAnchorV2 + parameters: + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: descendants + in: query + description: descendants + required: false + schema: + type: string + example: 3 + default: none + pattern: '^all$|^none$|^[0-9]+$|^-1$' + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: '#/components/examples/dataSample' + "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 + "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 + x-codegen-request-body-name: xpath /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: put: tags: @@ -1972,6 +2067,101 @@ paths: message: Internal Server Error details: Internal Server Error occurred x-codegen-request-body-name: xpath + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: + get: + tags: + - cps-query + summary: Query data nodes + description: Query data nodes for the given dataspace and anchor using CPS path + operationId: getNodesByDataspaceAndAnchorAndCpsPathV2 + parameters: + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: cps-path + in: query + description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container cps path: + value: //bookstore + list attributes cps path: + value: "//categories[@code=1]" + - name: descendants + in: query + description: descendants + required: false + schema: + type: string + pattern: '^all$|^none$|^[0-9]+$|^-1$' + example: false + default: none + example: 3 + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: '#/components/examples/dataSample' + "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 + "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 + x-codegen-request-body-name: xpath components: parameters: apiVersionInPath: |