From: mpriyank Date: Tue, 16 Sep 2025 09:42:39 +0000 (+0100) Subject: Fix for forward slash in id field of a resourceId X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=91d0bf45ec92dc45e8a36bb2801e2ef96c2fc0d3;p=cps%2Fncmp-dmi-plugin.git Fix for forward slash in id field of a resourceId - Reverted the logic to have a custom encoder as it was not supporting all the use cases - Now the client will properly encode the resourceId field in NCMP, which will result in DMI receiving the properly encoded resourceIdentifier - Encoded resourceId is then sent as a URI to preserve the encoding as needed by SDNC Issue-ID: CPS-2939 Change-Id: I13601ef0c306f0a5f23bf1fef410297076a4b282 Signed-off-by: mpriyank --- diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/RestTemplateAddressType.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/RestTemplateAddressType.java deleted file mode 100644 index 37be1564..00000000 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/RestTemplateAddressType.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * ============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.dmi.service.client; - -public enum RestTemplateAddressType { - - URL_STRING, URI - -} diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java index 2fbbce85..00bc21b6 100644 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java +++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java @@ -57,7 +57,7 @@ public class SdncRestconfClient { * @return the response entity */ public ResponseEntity getOperation(final String getResourceUrl, final HttpHeaders httpHeaders) { - return httpOperationWithJsonDataWithUri(HttpMethod.GET, getResourceUrl, null, httpHeaders); + return httpOperationWithJsonData(HttpMethod.GET, getResourceUrl, null, httpHeaders); } /** @@ -73,28 +73,11 @@ public class SdncRestconfClient { final String resourceUrl, final String jsonData, final HttpHeaders httpHeaders) { - return executeHttpOperation(httpMethod, resourceUrl, jsonData, httpHeaders, RestTemplateAddressType.URL_STRING); - } - - /** - * restconf http operations on sdnc. - * - * @param httpMethod HTTP Method - * @param resourceUrl sdnc resource url - * @param jsonData json data - * @param httpHeaders HTTP Headers - * @return response entity - */ - public ResponseEntity httpOperationWithJsonDataWithUri(final HttpMethod httpMethod, - final String resourceUrl, - final String jsonData, - final HttpHeaders httpHeaders) { - return executeHttpOperation(httpMethod, resourceUrl, jsonData, httpHeaders, RestTemplateAddressType.URI); + return executeHttpOperation(httpMethod, resourceUrl, jsonData, httpHeaders); } private ResponseEntity executeHttpOperation(final HttpMethod httpMethod, final String resourceUrl, - final String jsonData, final HttpHeaders httpHeaders, - final RestTemplateAddressType restTemplateAddressType) { + final String jsonData, final HttpHeaders httpHeaders) { final String sdncBaseUrl = sdncProperties.getBaseUrl(); final String sdncRestconfUrl = sdncBaseUrl.concat(resourceUrl); httpHeaders.setBasicAuth(sdncProperties.getAuthUsername(), sdncProperties.getAuthPassword()); @@ -102,12 +85,9 @@ public class SdncRestconfClient { final HttpEntity httpEntity = jsonData == null ? new HttpEntity<>(httpHeaders) : new HttpEntity<>(jsonData, httpHeaders); - if (RestTemplateAddressType.URI.equals(restTemplateAddressType)) { - final URI sdncRestconfUri = URI.create(sdncRestconfUrl); - log.debug("sdncRestconfUri: {}", sdncRestconfUri); - return restTemplate.exchange(sdncRestconfUri, httpMethod, httpEntity, String.class); - } - log.debug("sdncRestconfUrl: {}", sdncRestconfUrl); - return restTemplate.exchange(sdncRestconfUrl, httpMethod, httpEntity, String.class); + final URI sdncRestconfUri = URI.create(sdncRestconfUrl); + log.debug("sdncRestconfUri: {}", sdncRestconfUri); + return restTemplate.exchange(sdncRestconfUri, httpMethod, httpEntity, String.class); + } } diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoder.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoder.java deleted file mode 100644 index a00fece5..00000000 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoder.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * ============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.dmi.service.operation; - - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import org.springframework.web.util.UriUtils; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ResourceIdentifierEncoder { - - private static final String EQUALS_SIGN = "="; - private static final String PATH_SEPARATOR = "/"; - private static final String ENCODED_EQUALS = "%3D"; - - /** - * Encode a nested resource path by URL-encoding path segments while preserving - * key-value structure. Supports spaces, slashes, and '=' characters in values. - * - * @param rawResourcePath input path (may start with '/') - * @return encoded resource path - */ - public static String encodeNestedResourcePath(final String rawResourcePath) { - - final boolean hasLeadingPathSeparator = rawResourcePath.startsWith(PATH_SEPARATOR); - final String trimmedResourcePath = hasLeadingPathSeparator ? rawResourcePath.substring(1) : rawResourcePath; - - final List encodedSegments = parseAndEncodeSegments(trimmedResourcePath); - final String encodedResourcePath = String.join(PATH_SEPARATOR, encodedSegments); - - return hasLeadingPathSeparator ? PATH_SEPARATOR + encodedResourcePath : encodedResourcePath; - } - - private static List parseAndEncodeSegments(final String trimmedResourcePath) { - final List encodedResourcePathSegments = new ArrayList<>(); - final String[] resourcePathParts = trimmedResourcePath.split(PATH_SEPARATOR); - - int pathPartIndex = 0; - while (pathPartIndex < resourcePathParts.length) { - final String resourcePathPart = resourcePathParts[pathPartIndex]; - - if (resourcePathPart.contains(EQUALS_SIGN)) { - final StringBuilder resourcePathPartSegment = new StringBuilder(resourcePathPart); - pathPartIndex++; - // Continue collecting parts until we hit another key-value pair or end - while (pathPartIndex < resourcePathParts.length && !resourcePathParts[pathPartIndex].contains( - EQUALS_SIGN)) { - resourcePathPartSegment.append(PATH_SEPARATOR).append(resourcePathParts[pathPartIndex]); - pathPartIndex++; - } - encodedResourcePathSegments.add(encodePathSegment(resourcePathPartSegment.toString())); - } else { - // Simple resource path segment without equals - encodedResourcePathSegments.add(encodePathSegment(resourcePathPart)); - pathPartIndex++; - } - } - return encodedResourcePathSegments; - } - - private static String encodePathSegment(final String segment) { - if (segment.contains(EQUALS_SIGN)) { - return encodePathSegmentWithEqualsSign(segment); - } - return UriUtils.encodePathSegment(segment, StandardCharsets.UTF_8); - - } - - private static String encodePathSegmentWithEqualsSign(final String segment) { - final int indexOfEqualSign = segment.indexOf(EQUALS_SIGN); - final String key = segment.substring(0, indexOfEqualSign); - final String value = segment.substring(indexOfEqualSign + 1); - - // encode both key and value, and replace '=' with %3D in the value - final String encodedKey = UriUtils.encodePathSegment(key, StandardCharsets.UTF_8); - final String encodedValue = - UriUtils.encodePathSegment(value, StandardCharsets.UTF_8).replace(EQUALS_SIGN, ENCODED_EQUALS); - return encodedKey + EQUALS_SIGN + encodedValue; - } -} diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java index d6944971..45b7bb8d 100644 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java +++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java @@ -22,7 +22,6 @@ package org.onap.cps.ncmp.dmi.service.operation; import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum; -import static org.onap.cps.ncmp.dmi.service.operation.ResourceIdentifierEncoder.encodeNestedResourcePath; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.JsonPath; @@ -213,23 +212,18 @@ public class SdncOperations { private String prepareResourceDataUrl(final String nodeId, final String resourceId, final MultiValueMap queryMap) { - return addQuery(addResourceEncoded(addTopologyDataUrlwithNode(nodeId), resourceId), queryMap); + return addQuery(addResource(addTopologyDataUrlwithNode(nodeId), resourceId), queryMap); } private String addResource(final String url, final String resourceId) { + final String[] resourceIdAsPathSegments = resourceId.split("/"); + return UriComponentsBuilder.fromUriString(url) - .pathSegment(resourceId) + .pathSegment(resourceIdAsPathSegments) .buildAndExpand().toUriString(); } - private String addResourceEncoded(final String url, final String resourceId) { - - final String encodedNestedResourcePath = encodeNestedResourcePath(resourceId); - log.debug("Raw resourceId : {} , EncodedResourcePath : {}", resourceId, encodedNestedResourcePath); - return addResource(url, encodedNestedResourcePath); - } - private String addQuery(final String url, final MultiValueMap queryMap) { return UriComponentsBuilder diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoderSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoderSpec.groovy deleted file mode 100644 index aeca0622..00000000 --- a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoderSpec.groovy +++ /dev/null @@ -1,39 +0,0 @@ -/* - * ============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.dmi.service.operation - -import spock.lang.Specification - -class ResourceIdentifierEncoderSpec extends Specification { - - def 'encodeNestedResourcePath should handle valid paths correctly: #scenario'() { - when: 'we encode a valid resource path' - def result = ResourceIdentifierEncoder.encodeNestedResourcePath(input) - then: 'the result matches expectedEncodedString encoded format' - assert result == expectedEncodedString - where: 'following scenarios are used' - scenario | input || expectedEncodedString - 'simple path without leading path separator' | 'container' || 'container' - 'list entry with space in key value' | '/list-name=My Container/leaf=leaf with space' || '/list-name=My%20Container/leaf=leaf%20with%20space' - 'key value containing path separator' | '/container/list=id/with/slashes/leaf=Some Leaf' || '/container/list=id%2Fwith%2Fslashes/leaf=Some%20Leaf' - 'equals signs in key value' | '/list=Key=Value=Another' || '/list=Key%3DValue%3DAnother' - } -} diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy index 9dcb72e6..4997ae59 100644 --- a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy +++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy @@ -38,8 +38,8 @@ import spock.lang.Specification import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.CREATE import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.DELETE import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.PATCH -import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.UPDATE import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.READ +import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.UPDATE @SpringBootTest @ContextConfiguration(classes = [DmiConfiguration.SdncProperties, SdncOperations]) @@ -173,4 +173,19 @@ class SdncOperationsSpec extends Specification { '"," in value' | '(a=(x,y),b=y)' '"," in string value' | '(a="x,y",b=y)' } + + def 'adding resource identifier to the url path'() { + given: 'base url and resource identifier' + def baseUrl = 'my-base-url' + when: 'the resource identifier is split using forward slash and added as separate path segment' + def result = objectUnderTest.addResource(baseUrl, resourceIdentifier) + then: 'the url is passed as is and no further encoding/decoding takes place' + assert result == expectedUrl + where: 'following scenarios are used' + scenario | resourceIdentifier || expectedUrl + 'empty resource id' | '' || 'my-base-url' + 'forward slash' | '/' || 'my-base-url' + 'resource id with forward slash in between' | 'a/b' || 'my-base-url/a/b' + 'encoded slash in resource id' | 'a%2Fb/c' || 'my-base-url/a%2Fb/c' + } }