From 5faac48e9d6acfdad02b3cd65873d89389a956d6 Mon Sep 17 00:00:00 2001 From: seanbeirne Date: Tue, 12 Aug 2025 16:04:47 +0100 Subject: [PATCH] Manual Implementation of ProvMnS controller - Implementation of ProvMnS using spring Issue-ID: CPS-2966 Change-Id: Ia767b2779aa7b741230f295886f898246daf025c Signed-off-by: seanbeirne --- cps-ncmp-rest/.openapi-generator-ignore-provmns | 1 + .../NetworkCmProxyRestExceptionHandler.java | 6 + .../org/onap/cps/ncmp/rest/controller/ProvMnS.java | 286 +++++++++++++++++++++ .../ncmp/rest/controller/ProvMnsController.java | 35 ++- .../provmns/exception/InvalidPathException.java | 40 +++ .../ncmp/rest/util/ProvMnsRequestParameters.java | 66 +++++ .../NetworkCmProxyRestExceptionHandlerSpec.groovy | 3 + .../rest/controller/ProvMnsControllerSpec.groovy | 17 +- .../ncmp/provmns/ProvMnSRestApiSpec.groovy | 9 +- 9 files changed, 440 insertions(+), 23 deletions(-) create mode 100644 cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java create mode 100644 cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/exception/InvalidPathException.java create mode 100644 cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java diff --git a/cps-ncmp-rest/.openapi-generator-ignore-provmns b/cps-ncmp-rest/.openapi-generator-ignore-provmns index 1f910ae19b..2c7ad93b7e 100644 --- a/cps-ncmp-rest/.openapi-generator-ignore-provmns +++ b/cps-ncmp-rest/.openapi-generator-ignore-provmns @@ -1,5 +1,6 @@ # Ignore generation of all the models for ProvMns target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/*.java +target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/api/*.java # Allow generation of the below model for ProvMns !target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ClassNameIdPatchDefaultResponse.java diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java index 4b5ef173c9..4bb07b607e 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java @@ -41,6 +41,7 @@ import org.onap.cps.ncmp.api.exceptions.ServerNcmpException; import org.onap.cps.ncmp.rest.model.DmiErrorMessage; import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiResponse; import org.onap.cps.ncmp.rest.model.ErrorMessage; +import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -101,6 +102,11 @@ public class NetworkCmProxyRestExceptionHandler { return buildErrorResponse(HttpStatus.PAYLOAD_TOO_LARGE, exception); } + @ExceptionHandler({InvalidPathException.class}) + public static ResponseEntity invalidPathExceptions(final Exception exception) { + return buildErrorResponse(HttpStatus.UNPROCESSABLE_ENTITY, exception); + } + private static ResponseEntity buildErrorResponse(final HttpStatus status, final Exception exception) { if (exception.getCause() != null || !(exception instanceof CpsException)) { log.error("Exception occurred", exception); diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java new file mode 100644 index 0000000000..77109b58f3 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java @@ -0,0 +1,286 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import java.util.List; +import org.onap.cps.ncmp.rest.model.ErrorMessage; +import org.onap.cps.ncmp.rest.provmns.model.ClassNameIdGetDataNodeSelectorParameter; +import org.onap.cps.ncmp.rest.provmns.model.ClassNameIdPatchDefaultResponse; +import org.onap.cps.ncmp.rest.provmns.model.ErrorResponseDefault; +import org.onap.cps.ncmp.rest.provmns.model.ErrorResponseGet; +import org.onap.cps.ncmp.rest.provmns.model.Resource; +import org.onap.cps.ncmp.rest.provmns.model.Scope; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@Tag(name = "ProvMnS", description = "Provisioning Management Service") +public interface ProvMnS { + + /** + * DELETE /{URI-LDN-first-part}/{className}={id} : Deletes one resource + * With HTTP DELETE one resource is deleted. The resources to be deleted is identified with the target URI. + * + * @param httpServletRequest (required) + * @return Success case "200 OK". This status code is returned, when the resource has been successfully deleted. + * The response body is empty. (status code 200) + * or Error case. (status code 200) + */ + @Operation( + operationId = "deleteMoi", + summary = "Deletes one resource", + description = "With HTTP DELETE one resource is deleted. " + + "The resources to be deleted is identified with the target URI.", + responses = { + @ApiResponse(responseCode = "200", + description = "Success case (\"200 OK\"). This status code is returned, " + + "when the resource has been successfully deleted. The response body is empty."), + @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) + }), + @ApiResponse(responseCode = "default", description = "Error case.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDefault.class)) + }) + } + ) + @DeleteMapping( + value = "v1/**", + produces = { "application/json" } + ) + ResponseEntity deleteMoi(HttpServletRequest httpServletRequest); + + + /** + * GET /{URI-LDN-first-part}/{className}={id} : Reads one or multiple resources + * With HTTP GET resources are read. The resources to be retrieved are identified with the target URI. + * The attributes and fields parameter of the query components allow + * to select the resource properties to be returned. + * + * @param httpServletRequest (required) + * @param scope This parameter extends the set of targeted resources beyond the base resource identified + * with the path component of the URI. + * No scoping mechanism is specified in the present document. (optional) + * @param filter This parameter reduces the targeted set of resources by applying a filter to the scoped set of + * resource representations. Only resource representations for which the filter construct evaluates + * to "true" are targeted. (optional) + * @param attributes This parameter specifies the attributes of the scoped resources that are returned. (optional) + * @param fields This parameter specifies the attribute field of the scoped resources that are returned. (optional) + * @param dataNodeSelector This parameter contains an expression allowing + * to conditionally select data nodes. (optional) + * @return Success case "200 OK". + * The resources identified in the request for retrieval are returned in the response message body. + * In case the attributes or fields query parameters are used, + * only the selected attributes or sub-attributes are returned. + * The response message body is constructed according to the hierarchical response + * construction method (TS 32.158 [15]). (status code 200) or Error case. (status code 200) + */ + @Operation( + operationId = "getMoi", + summary = "Reads one or multiple resources", + description = "With HTTP GET resources are read. " + + "The resources to be retrieved are identified with the target URI. " + + "The attributes and fields parameter of the query components allow" + + " to select the resource properties to be returned.", + responses = { + @ApiResponse(responseCode = "200", description = "Success case (\"200 OK\"). " + + "The resources identified in the request for retrieval are returned in the response message body. " + + "In case the attributes or fields query parameters are used, " + + "only the selected attributes or sub-attributes are returned. " + + "The response message body is constructed according to the " + + "hierarchical response construction method (TS 32.158 [15]).", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = Resource.class)) + }), + @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) + }), + @ApiResponse(responseCode = "default", description = "Error case.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseGet.class)) + }) + } + ) + @GetMapping( + value = "v1/**", + produces = { "application/json"} + ) + + ResponseEntity getMoi( + HttpServletRequest httpServletRequest, + @Parameter(name = "scope", description = "This parameter extends the set of targeted resources beyond the " + + "base resource identified with the path component of the URI. " + + "No scoping mechanism is specified in the present document.", in = ParameterIn.QUERY) @Valid Scope scope, + @Parameter(name = "filter", description = "This parameter reduces the targeted set of resources by applying" + + " a filter to the scoped set of resource representations. " + + "Only resource representations for which the filter construct evaluates to \"true\" are targeted.", + in = ParameterIn.QUERY) @Valid @RequestParam(value = "filter", required = false) String filter, + @Parameter(name = "attributes", description = "This parameter specifies the attributes of the scoped " + + "resources that are returned.", in = ParameterIn.QUERY) + @Valid @RequestParam(value = "attributes", required = false) + List attributes, + @Parameter(name = "fields", description = "This parameter specifies the attribute field " + + "of the scoped resources that are returned.", in = ParameterIn.QUERY) + @Valid @RequestParam(value = "fields", required = false) List fields, + @Parameter(name = "dataNodeSelector", description = "This parameter contains an expression " + + "allowing to conditionally select data nodes.", in = ParameterIn.QUERY) @Valid + ClassNameIdGetDataNodeSelectorParameter dataNodeSelector + ); + + + /** + * PATCH /{URI-LDN-first-part}/{className}={id} : Patches one or multiple resources + * With HTTP PATCH resources are created, updated or deleted. + * The resources to be modified are identified with the target URI (base resource) + * and the patch document included in the request message body. + * + * @param httpServletRequest (required) + * @param resource The request body describes changes to be made to the target resources. + * The following patch media types are available + * - "application/json-patch+json" (RFC 6902) + * - "application/3gpp-json-patch+json" (TS 32.158) (required) + * @return Success case ("200 OK"). + * This status code is returned when the updated resource representations + * shall be returned for some reason. + * The resource representations are returned in the response message body. + * The response message body is constructed according to the hierarchical + * response construction method (TS 32.158 [15]) (status code 200) + * or Success case ("204 No Content"). This status code is returned when there is no need to + * return the updated resource representations. The response message body is empty. (status code 204) + * or Error case. (status code 200) + */ + @Operation( + operationId = "patchMoi", + summary = "Patches one or multiple resources", + description = "With HTTP PATCH resources are created, updated or deleted. " + + "The resources to be modified are identified with the target URI (base resource) " + + "and the patch document included in the request message body.", + responses = { + @ApiResponse(responseCode = "200", description = "Success case (\"200 OK\"). " + + "This status code is returned when the updated the resource representations shall be returned " + + "for some reason. The resource representations are returned in the response message body. The " + + "response message body is constructed according to the hierarchical response construction method " + + "(TS 32.158 [15])", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = Resource.class)) + }), + @ApiResponse(responseCode = "204", description = "Success case (\"204 No Content\"). " + + "This status code is returned when there is no need to return the updated resource representations. " + + "The response message body is empty."), + @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) + }), + @ApiResponse(responseCode = "default", description = "Error case.", content = { + @Content(mediaType = "application/json", + schema = @Schema(implementation = ClassNameIdPatchDefaultResponse.class)) + }) + } + ) + @PatchMapping( + value = "v1/**", + produces = { "application/json" }, + consumes = {"application/json-patch+json", "application/3gpp-json-patch+json" } + ) + + ResponseEntity patchMoi( + HttpServletRequest httpServletRequest, + @Parameter(name = "Resource", description = "The request body describes changes to be made to the target " + + "resources. The following patch media types are available " + + "- \"application/json-patch+json\" (RFC 6902) " + + "- \"application/3gpp-json-patch+json\" (TS 32.158)", required = true) @Valid @RequestBody + Resource resource + ); + + + /** + * PUT /{URI-LDN-first-part}/{className}={id} : Replaces a complete single resource or + * creates it if it does not exist + * With HTTP PUT a complete resource is replaced or created if it does not exist. + * The target resource is identified by the target URI. + * + * @param httpServletRequest (required) + * @param resource (required) + * @return Success case ("200 OK"). This status code shall be returned when the resource is replaced, + * and when the replaced resource representation is not identical to the resource representation in + * the request. This status code may be returned when the resource is updated and when the updated + * resource representation is identical to the resource representation in the request. + * The representation of the updated resource is returned in the response message body. (status code 200) + * or Success case ("201 Created"). This status code shall be returned when the resource + * is created. + * The representation of the created resource is returned in the response message body. (status code 201) + * or Success case ("204 No Content"). This status code may be returned only when the replaced + * resource representation is identical to the representation in the request. + * The response has no message body. (status code 204) + * or Error case. (status code 200) + */ + @Operation( + operationId = "putMoi", + summary = "Replaces a complete single resource or creates it if it does not exist", + description = "With HTTP PUT a complete resource is replaced or created if it does not exist. " + + "The target resource is identified by the target URI.", + responses = { + @ApiResponse(responseCode = "200", description = "Success case (\"200 OK\"). " + + "This status code shall be returned when the resource is replaced, and when the replaced " + + "resource representation is not identical to the resource representation in the request. " + + "This status code may be returned when the resource is updated and when the updated resource " + + "representation is identical to the resource representation in the request. " + + "The representation of the updated resource is returned in the response message body.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = Resource.class)) + }), + @ApiResponse(responseCode = "201", description = "Success case (\"201 Created\"). " + + "This status code shall be returned when the resource is created. The representation of" + + " the created resource is returned in the response message body.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = Resource.class)) + }), + @ApiResponse(responseCode = "204", description = "Success case (\"204 No Content\"). " + + "This status code may be returned only when the replaced resource representation is identical " + + "to the representation in the request. The response has no message body."), + @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) + }), + @ApiResponse(responseCode = "default", description = "Error case.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDefault.class)) + }) + } + ) + @PutMapping( + value = "v1/**", + produces = { "application/json" }, + consumes = { "application/json" } + ) + + ResponseEntity putMoi( + HttpServletRequest httpServletRequest, + @Parameter(name = "Resource", + description = "The request body describes the resource that has been created or replaced", required = true) + @Valid @RequestBody Resource resource + ); + +} \ No newline at end of file diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java index 042254462a..d7193f0b03 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java @@ -21,41 +21,44 @@ package org.onap.cps.ncmp.rest.controller; +import jakarta.servlet.http.HttpServletRequest; import java.util.List; import lombok.RequiredArgsConstructor; -import org.onap.cps.ncmp.rest.provmns.api.DefaultApi; +import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.rest.provmns.model.ClassNameIdGetDataNodeSelectorParameter; import org.onap.cps.ncmp.rest.provmns.model.Resource; import org.onap.cps.ncmp.rest.provmns.model.Scope; +import org.onap.cps.ncmp.rest.util.ProvMnsRequestParameters; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Slf4j @RestController @RequestMapping("${rest.api.provmns-base-path}") @RequiredArgsConstructor -public class ProvMnsController implements DefaultApi { +public class ProvMnsController implements ProvMnS { /** * Replaces a complete single resource or creates it if it does not exist. * - * @param className Class name of the targeted resource - * @param id Identifier of the targeted resource + * @param httpServletRequest URI request including path * @param resource Resource representation of the resource to be created or replaced * @return {@code ResponseEntity} The representation of the updated resource is returned in the response * message body. */ @Override - public ResponseEntity classNameidPut(final String className, final String id, final Resource resource) { + public ResponseEntity putMoi(final HttpServletRequest httpServletRequest, final Resource resource) { + final ProvMnsRequestParameters provMnsRequestParameters = + ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } /** * Reads one or multiple resources. * - * @param className Class name of the targeted resource - * @param id Identifier of the targeted resource + * @param httpServletRequest URI request including path * @param scope Extends the set of targeted resources beyond the base * resource identified with the authority and path component of * the URI. @@ -73,35 +76,39 @@ public class ProvMnsController implements DefaultApi { * in the response message body. */ @Override - public ResponseEntity classNameidGet(final String className, final String id, final Scope scope, + public ResponseEntity getMoi(final HttpServletRequest httpServletRequest, final Scope scope, final String filter, final List attributes, final List fields, final ClassNameIdGetDataNodeSelectorParameter dataNodeSelector) { + final ProvMnsRequestParameters requestParameters = + ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } /** * Patches (Create, Update or Delete) one or multiple resources. * - * @param className Class name of the targeted resource - * @param id Identifier of the targeted resource + * @param httpServletRequest URI request including path * @param resource Resource representation of the resource to be created or replaced * @return {@code ResponseEntity} The updated resource representations are returned in the response message body. */ @Override - public ResponseEntity classNameidPatch(final String className, final String id, final Resource resource) { + public ResponseEntity patchMoi(final HttpServletRequest httpServletRequest, final Resource resource) { + final ProvMnsRequestParameters requestParameters = + ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } /** * Delete one or multiple resources. * - * @param className Class name of the targeted resource - * @param id Identifier of the targeted resource + * @param httpServletRequest URI request including path * @return {@code ResponseEntity} The response body is empty, HTTP status returned. */ @Override - public ResponseEntity classNameidDelete(final String className, final String id) { + public ResponseEntity deleteMoi(final HttpServletRequest httpServletRequest) { + final ProvMnsRequestParameters requestParameters = + ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/exception/InvalidPathException.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/exception/InvalidPathException.java new file mode 100644 index 0000000000..f9cc5cca57 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/exception/InvalidPathException.java @@ -0,0 +1,40 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.provmns.exception; + +import org.onap.cps.ncmp.api.exceptions.NcmpException; + +public class InvalidPathException extends NcmpException { + + private static final String INVALID_PATH_DETAILS_FORMAT = + "%s not a valid path"; + + /** + * Constructor. + * + * @param path provmns uri path + */ + public InvalidPathException(final String path) { + super("not a valid path", String.format(INVALID_PATH_DETAILS_FORMAT, + path)); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java new file mode 100644 index 0000000000..3de9a4461a --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java @@ -0,0 +1,66 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.util; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Getter; +import lombok.Setter; +import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException; + +@Getter +@Setter +public class ProvMnsRequestParameters { + + private String uriLdnFirstPart; + private String className; + private String id; + + private static final String PROVMNS_BASE_PATH = "ProvMnS/v\\d+/"; + + /** + * Converts HttpServletRequest to ProvMnsRequestParameters. + * + * @param httpServletRequest HttpServletRequest object containing the path + * @return ProvMnsRequestParameters object containing parsed parameters + */ + public static ProvMnsRequestParameters toProvMnsRequestParameters(final HttpServletRequest httpServletRequest) { + final String uriPath = (String) httpServletRequest.getAttribute( + "org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping"); + + final String[] pathVariables = uriPath.split(PROVMNS_BASE_PATH); + final int lastSlashIndex = pathVariables[1].lastIndexOf('/'); + if (lastSlashIndex == -1) { + throw new InvalidPathException(uriPath); + } + final ProvMnsRequestParameters provMnsRequestParameters = new ProvMnsRequestParameters(); + provMnsRequestParameters.setUriLdnFirstPart(pathVariables[1].substring(0, lastSlashIndex)); + final String classNameAndId = pathVariables[1].substring(lastSlashIndex + 1); + + final String[] splitClassNameId = classNameAndId.split("=", 2); + if (splitClassNameId.length != 2) { + throw new InvalidPathException(uriPath); + } + provMnsRequestParameters.setClassName(splitClassNameId[0]); + provMnsRequestParameters.setId(splitClassNameId[1]); + + return provMnsRequestParameters; + } +} diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy index 7b16df52df..f1f4c9195d 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -36,6 +36,7 @@ import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException import org.onap.cps.ncmp.rest.util.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper @@ -64,6 +65,7 @@ import static org.springframework.http.HttpStatus.CONFLICT import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR import static org.springframework.http.HttpStatus.NOT_FOUND import static org.springframework.http.HttpStatus.PAYLOAD_TOO_LARGE +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post @@ -148,6 +150,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist' 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs' 'Policy Executor' | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails, null) || CONFLICT | sampleErrorMessage | sampleErrorDetails + 'Invalid Path' | new InvalidPathException("some invalid path") || UNPROCESSABLE_ENTITY | 'not a valid path' | 'some invalid path not a valid path' } def 'Post request with exception returns correct HTTP Status.'() { diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy index fbaa0c1bec..d87dc806b3 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy @@ -23,7 +23,6 @@ package org.onap.cps.ncmp.rest.controller import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.rest.provmns.model.ResourceOneOf import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest @@ -50,7 +49,7 @@ class ProvMnsControllerSpec extends Specification { def 'Get Resource Data from provmns interface.'() { given: 'resource data url' - def getUrl = "$provMnSBasePath/test=another" + def getUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" when: 'get data resource request is performed' def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response then: 'response status is Not Implemented (501)' @@ -59,7 +58,7 @@ class ProvMnsControllerSpec extends Specification { def 'Put Resource Data from provmns interface.'() { given: 'resource data url' - def putUrl = "$provMnSBasePath/test=another" + def putUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" and: 'an example resource json object' def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test')) when: 'put data resource request is performed' @@ -73,7 +72,7 @@ class ProvMnsControllerSpec extends Specification { def 'Patch Resource Data from provmns interface.'() { given: 'resource data url' - def patchUrl = "$provMnSBasePath/test=another" + def patchUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" and: 'an example resource json object' def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test')) when: 'patch data resource request is performed' @@ -87,11 +86,19 @@ class ProvMnsControllerSpec extends Specification { def 'Delete Resource Data from provmns interface.'() { given: 'resource data url' - def deleteUrl = "$provMnSBasePath/test=another" + def deleteUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" when: 'delete data resource request is performed' def response = mvc.perform(delete(deleteUrl)).andReturn().response then: 'response status is Not Implemented (501)' assert response.status == HttpStatus.NOT_IMPLEMENTED.value() } + def 'Get Resource Data from provmns interface with query param.'() { + given: 'resource data url with query parameter' + def getUrl = "$provMnSBasePath/v1/A=1/B=2/C=3?attributes=[test,query,param]" + when: 'get data resource request is performed' + def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response + then: 'response status is Not Implemented (501)' + assert response.status == HttpStatus.NOT_IMPLEMENTED.value() + } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy index 29222a9dd2..474a480a43 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy @@ -30,11 +30,12 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +@SuppressWarnings('SpellCheckingInspection') class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{ def 'Get Resource Data from provmns interface.'() { expect: 'not implemented response on GET endpoint' - mvc.perform(get("/ProvMnS/SampleClassName=SampleId")) + mvc.perform(get("/ProvMnS/v1/A=1/B=2/C=3")) .andExpect(status().isNotImplemented()) } @@ -42,7 +43,7 @@ class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{ given: 'an example resource json body' def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test')) expect: 'not implemented response on PUT endpoint' - mvc.perform(put("/ProvMnS/SampleClassName=SampleId") + mvc.perform(put("/ProvMnS/v1/A=1/B=2/C=3") .contentType(MediaType.APPLICATION_JSON) .content(jsonBody)) .andExpect(status().isNotImplemented()) @@ -52,7 +53,7 @@ class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{ given: 'an example resource json body' def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test')) expect: 'not implemented response on PATCH endpoint' - mvc.perform(patch("/ProvMnS/SampleClassName=SampleId") + mvc.perform(patch("/ProvMnS/v1/A=1/B=2/C=3") .contentType(new MediaType('application', 'json-patch+json')) .content(jsonBody)) .andExpect(status().isNotImplemented()) @@ -60,7 +61,7 @@ class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{ def 'Delete Resource Data from provmns interface.'() { expect: 'not implemented response on DELETE endpoint' - mvc.perform(delete("/ProvMnS/SampleClassName=SampleId")) + mvc.perform(delete("/ProvMnS/v1/A=1/B=2/C=3")) .andExpect(status().isNotImplemented()) } } -- 2.16.6