Fix for forward slash in id field of a resourceId 56/142056/4 master
authormpriyank <priyank.maheshwari@est.tech>
Tue, 16 Sep 2025 09:42:39 +0000 (10:42 +0100)
committermpriyank <priyank.maheshwari@est.tech>
Wed, 17 Sep 2025 10:44:41 +0000 (11:44 +0100)
- 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 <priyank.maheshwari@est.tech>
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/RestTemplateAddressType.java [deleted file]
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoder.java [deleted file]
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java
dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/ResourceIdentifierEncoderSpec.groovy [deleted file]
dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy

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 (file)
index 37be156..0000000
+++ /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
-
-}
index 2fbbce8..00bc21b 100644 (file)
@@ -57,7 +57,7 @@ public class SdncRestconfClient {
      * @return the response entity
      */
     public ResponseEntity<String> 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<String> 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<String> 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<String> 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 (file)
index a00fece..0000000
+++ /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<String> encodedSegments = parseAndEncodeSegments(trimmedResourcePath);
-        final String encodedResourcePath = String.join(PATH_SEPARATOR, encodedSegments);
-
-        return hasLeadingPathSeparator ? PATH_SEPARATOR + encodedResourcePath : encodedResourcePath;
-    }
-
-    private static List<String> parseAndEncodeSegments(final String trimmedResourcePath) {
-        final List<String> 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;
-    }
-}
index d694497..45b7bb8 100644 (file)
@@ -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<String, String> 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<String, String> 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 (file)
index aeca062..0000000
+++ /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'
-    }
-}
index 9dcb72e..4997ae5 100644 (file)
@@ -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'
+    }
 }