Additional validation for names/identifiers 94/127594/31
authorDylanB95EST <dylan.byrne@est.tech>
Tue, 8 Mar 2022 17:26:34 +0000 (17:26 +0000)
committerDylanB95EST <dylan.byrne@est.tech>
Wed, 30 Mar 2022 11:01:00 +0000 (12:01 +0100)
Implementing additional regex validation for names/identifiers
in CPS

- Introduces a CpsValidator
- Applies to relevent java public API's
- Tests have been updated where necessary

Issue-ID: CPS-322
Change-Id: I29ab8820bb1fe0eee247e425d6bb018bcd38f28e
Signed-off-by: DylanB95EST <dylan.byrne@est.tech>
25 files changed:
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
cps-ncmp-rest/src/test/resources/dmi_registration_all_singing_and_dancing.json
cps-ncmp-rest/src/test/resources/dmi_registration_updates_only.json
cps-ncmp-rest/src/test/resources/dmi_registration_without_properties.json
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy
cps-rest/docs/openapi/cpsAdmin.yml
cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java
cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy [new file with mode: 0644]

index b642370..77482d0 100644 (file)
@@ -28,7 +28,6 @@ import org.onap.cps.TestUtils
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
 import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
 import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
index fd8b56b..c2a307d 100644 (file)
@@ -3,7 +3,7 @@
   "dmiModelPlugin":"service3",
   "createdCmHandles":[
     {
-      "cmHandle":"ch1(new)",
+      "cmHandle":"ch1-new",
       "cmHandleProperties":{
         "dmiProp1":"ch1-dmi1",
         "dmiProp2":"ch1-dmi2"
@@ -14,7 +14,7 @@
       }
     },
     {
-      "cmHandle":"ch2(new)",
+      "cmHandle":"ch2-new",
       "cmHandleProperties":{
         "dmiProp1":"ch2-dmi1",
         "dmiProp2":"ch2-dmi2"
@@ -27,7 +27,7 @@
   ],
   "updatedCmHandles":[
     {
-      "cmHandle":"ch3(upd)",
+      "cmHandle":"ch3-upd",
       "cmHandleProperties":{
         "dmiProp1":"ch3-dmi1"
       },
index 58a1a98..26acdbd 100644 (file)
@@ -2,7 +2,7 @@
   "dmiPlugin": "service1",
   "updatedCmHandles":[
     {
-      "cmHandle":"ch3(upd)",
+      "cmHandle":"ch3-upd",
       "cmHandleProperties":{
         "dmiProp1":"ch3-dmi1",
         "dmiProp2":null
index 395c098..a5dd7b0 100644 (file)
@@ -4,7 +4,7 @@
   "dmiModelPlugin":"service3",
   "createdCmHandles":[
     {
-      "cmHandle": "ch1(new)"
+      "cmHandle": "ch1-new"
     }
   ]
 }
index c3369d8..3964144 100755 (executable)
@@ -60,8 +60,10 @@ import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.ModuleReference;
+import org.onap.cps.utils.CpsValidator;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -119,7 +121,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                                                         final String acceptParamInHeader,
                                                         final String optionsParamInQuery,
                                                         final String topicParamInQuery) {
-
+        CpsValidator.validateNameCharacters(cmHandleId);
         return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader,
                 DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery);
     }
@@ -130,6 +132,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                                                                final String acceptParamInHeader,
                                                                final String optionsParamInQuery,
                                                                final String topicParamInQuery) {
+        CpsValidator.validateNameCharacters(cmHandleId);
         return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader,
                 DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery);
     }
@@ -140,6 +143,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                                                                final OperationEnum operation,
                                                                final String requestData,
                                                                final String dataType) {
+        CpsValidator.validateNameCharacters(cmHandleId);
         return handleResponse(
             dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(
                 cmHandleId, resourceIdentifier, operation, requestData, dataType),
@@ -149,6 +153,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
     @Override
     public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
+        CpsValidator.validateNameCharacters(cmHandleId);
         return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
     }
 
@@ -171,6 +176,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      */
     @Override
     public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) {
+        CpsValidator.validateNameCharacters(cmHandleId);
         final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
         final YangModelCmHandle yangModelCmHandle =
             yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
@@ -235,6 +241,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
     private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) {
         try {
+            CpsValidator.validateNameCharacters(yangModelCmHandle.getId());
             final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}",
                 jsonObjectMapper.asJsonString(yangModelCmHandle));
             cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
@@ -244,6 +251,9 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         } catch (final AlreadyDefinedException alreadyDefinedException) {
             return CmHandleRegistrationResponse.createFailureResponse(
                 yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST);
+        } catch (final DataValidationException dataValidationException) {
+            return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(),
+                RegistrationError.CM_HANDLE_INVALID_ID);
         } catch (final Exception exception) {
             return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception);
         }
@@ -260,6 +270,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
             new ArrayList<>(tobeRemovedCmHandles.size());
         for (final String cmHandle : tobeRemovedCmHandles) {
             try {
+                CpsValidator.validateNameCharacters(cmHandle);
                 deleteSchemaSetWithCascade(cmHandle);
                 cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                     "/dmi-registry/cm-handles[@id='" + cmHandle + "']", NO_TIMESTAMP);
@@ -269,8 +280,13 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                     cmHandle, dataNodeNotFoundException.getMessage());
                 cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
                     .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST));
+            } catch (final DataValidationException dataValidationException) {
+                log.error("Unable to de-register cm-handle id: {}, caused by: {}",
+                    cmHandle, dataValidationException.getMessage());
+                cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
+                    .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_INVALID_ID));
             } catch (final Exception exception) {
-                log.error("Unable to de-register cm-handleIdd : {} , caused by : {}",
+                log.error("Unable to de-register cm-handle id : {} , caused by : {}",
                     cmHandle, exception.getMessage());
                 cmHandleRegistrationResponses.add(
                     CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception));
index c838a75..ff79f87 100644 (file)
@@ -45,8 +45,10 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
+import org.onap.cps.utils.CpsValidator;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -72,6 +74,7 @@ public class NetworkCmProxyDataServicePropertyHandler {
         for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
             final String cmHandle = ncmpServiceCmHandle.getCmHandleID();
             try {
+                CpsValidator.validateNameCharacters(cmHandle);
                 final String cmHandleXpath = String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandle);
                 final DataNode existingCmHandleDataNode =
                         cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXpath,
@@ -83,8 +86,14 @@ public class NetworkCmProxyDataServicePropertyHandler {
                     cmHandle, e.getMessage());
                 cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
                     .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST));
+            } catch (final DataValidationException e) {
+                log.error("Unable to update cm handle : {}, caused by : {}",
+                    cmHandle, e.getMessage());
+                cmHandleRegistrationResponses.add(
+                    CmHandleRegistrationResponse.createFailureResponse(cmHandle,
+                        RegistrationError.CM_HANDLE_INVALID_ID));
             } catch (final Exception exception) {
-                log.error("Unable to update dataNode for cmHandleId : {} , caused by : {}",
+                log.error("Unable to update cmHandle : {} , caused by : {}",
                     cmHandle, exception.getMessage());
                 cmHandleRegistrationResponses.add(
                     CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception));
index e183ed1..1da2aa9 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Bell Canada
+ *  Modifications Copyright (C) 2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -77,7 +78,8 @@ public class CmHandleRegistrationResponse {
     public enum RegistrationError {
         UNKNOWN_ERROR("00", "Unknown error"),
         CM_HANDLE_ALREADY_EXIST("01", "cm-handle already exists"),
-        CM_HANDLE_DOES_NOT_EXIST("02", "cm-handle does not exist");
+        CM_HANDLE_DOES_NOT_EXIST("02", "cm-handle does not exist"),
+        CM_HANDLE_INVALID_ID("03", "cm-handle has an invalid character(s) in id");
 
         public final String errorCode;
         public final String errorText;
index e7c1d05..cb4d5ef 100644 (file)
@@ -35,6 +35,7 @@ import org.onap.cps.ncmp.api.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
+import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Shared
@@ -42,6 +43,7 @@ import spock.lang.Specification
 
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_ALREADY_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_INVALID_ID
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED
@@ -218,7 +220,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
     def 'Create CM-Handle Error Handling: Registration fails: #scenario'() {
         given: 'a registration without cm-handle properties'
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
-            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')]
+            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: cmHandleId)]
         and: 'cm-handler registration fails: #scenario'
             mockCpsDataService.saveListElements(_, _, _, _, _) >> { throw exception }
         when: 'registration is updated'
@@ -227,16 +229,17 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             response.getCreatedCmHandles().size() == 1
             with(response.getCreatedCmHandles().get(0)) {
                 assert it.status == Status.FAILURE
-                assert it.cmHandle == 'cmhandle'
+                assert it.cmHandle ==  cmHandleId
                 assert it.registrationError == expectedError
                 assert it.errorText == expectedErrorText
             }
         and: 'model-sync is not invoked'
             0 * objectUnderTest.syncModulesAndCreateAnchor(_)
         where:
-            scenario                                        | exception                                               || expectedError           | expectedErrorText
-            'cm-handle already exist'                       | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
-            'unknown exception while registering cm-handle' | new RuntimeException('Failed')                          || UNKNOWN_ERROR           | 'Failed'
+            scenario                                        | cmHandleId             | exception                                               || expectedError           | expectedErrorText
+            'cm-handle already exist'                       | 'cmhandle'             | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
+            'cm-handle has invalid name'                    | 'cm handle with space' | new DataValidationException("", "")                     || CM_HANDLE_INVALID_ID    | 'cm-handle has an invalid character(s) in id'
+            'unknown exception while registering cm-handle' | 'cmhandle'             | new RuntimeException('Failed')                          || UNKNOWN_ERROR           | 'Failed'
     }
 
     def 'Create CM-Handle Error Handling: Model Sync fails'() {
@@ -268,12 +271,13 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         and: 'cm-handle updates can be processed successfully'
             def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'),
                                            CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")),
-                                           CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLE_DOES_NOT_EXIST)]
+                                           CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLE_DOES_NOT_EXIST),
+                                           CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)]
             mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse
         when: 'registration is updated'
             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'the response contains updateOperationResponse'
-            assert response.getUpdatedCmHandles().size() == 3
+            assert response.getUpdatedCmHandles().size() == 4
             assert response.getUpdatedCmHandles().containsAll(updateOperationResponse)
     }
 
@@ -369,9 +373,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
                 assert it.errorText == expectedErrorText
             }
         where:
-            scenario                   | deleteListElementException                | expectedError            | expectedErrorText
-            'cm-handle does not exist' | new DataNodeNotFoundException("", "", "") | CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
-            'an unexpected exception'  | new RuntimeException("Failed")            | UNKNOWN_ERROR            | 'Failed'
+            scenario                     | cmHandleId             | deleteListElementException                ||  expectedError           | expectedErrorText
+            'cm-handle does not exist'   | 'cmhandle'             | new DataNodeNotFoundException("", "", "") || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
+            'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "")       || CM_HANDLE_INVALID_ID     | 'cm-handle has an invalid character(s) in id'
+            'an unexpected exception'    | 'cmhandle'             | new RuntimeException("Failed")            || UNKNOWN_ERROR            | 'Failed'
     }
 
     def getObjectUnderTestWithModelSyncDisabled() {
index c21d7e7..ded4943 100644 (file)
@@ -286,9 +286,9 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def 'Getting Yang Resources.'() {
         when: 'yang resources is called'
-            objectUnderTest.getYangResourcesModuleReferences('some cm handle')
+            objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
         then: 'CPS module services is invoked for the correct dataspace and cm handle'
-            1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some cm handle')
+            1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some-cm-handle')
     }
 
     def 'Get cm handle identifiers for the given module names.'() {
index f6264f4..7aacbda 100644 (file)
 
 package org.onap.cps.ncmp.api.impl
 
+import org.onap.cps.spi.exceptions.DataValidationException
+
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_INVALID_ID
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
 
@@ -118,7 +121,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
             'no original properties'              | []                        || 0
     }
 
-    def 'Exception thrown when we try to update cmHandle'() {
+    def '#scenario error leads to #exception when we try to update cmHandle'() {
         given: 'cm handles request'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: [:], dmiProperties: [:])]
         and: 'data node cannot be found'
@@ -135,9 +138,10 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
                 assert it.errorText == expectedErrorText
             }
         where:
-            scenario                  | exception                                                        || expectedError            | expectedErrorText
-            'cmhandle does not exist' | new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
-            'unexpected error'        | new RuntimeException('Failed')                                   || UNKNOWN_ERROR            | 'Failed'
+            scenario                   | cmHandleId               | exception                                                                                           || expectedError            | expectedErrorText
+            'Cm Handle does not exist' | 'cmHandleId'             | new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry')                                    || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
+            'Unknown'                  | 'cmHandleId'             | new RuntimeException('Failed')                                                                      || UNKNOWN_ERROR            | 'Failed'
+            'Invalid cm handle id'     | 'cmHandleId with spaces' | new DataValidationException('Name Validation Error.', cmHandleId + 'contains an invalid character') || CM_HANDLE_INVALID_ID     | 'cm-handle has an invalid character(s) in id'
     }
 
     def 'Multiple update operations in a single request'() {
index c5ef2f4..4476998 100644 (file)
@@ -26,8 +26,8 @@ import spock.lang.Specification
 
 class CmHandleRegistrationResponseSpec extends Specification {
 
-    def 'Successful CmHandle Registration Response'() {
-        when: 'CMHandle response is created'
+    def 'Successful cm-handle Registration Response'() {
+        when: 'cm-handle response is created'
             def cmHandleRegistrationResponse = CmHandleRegistrationResponse.createSuccessResponse('cmHandle')
         then: 'a success response is returned'
             with(cmHandleRegistrationResponse) {
@@ -39,8 +39,8 @@ class CmHandleRegistrationResponseSpec extends Specification {
             cmHandleRegistrationResponse.errorText == null
     }
 
-    def 'Failed Cm Handle Registration Response: for unexpected exception'() {
-        when: 'CMHandle response is created for an unexpected exception'
+    def 'Failed cm-handle Registration Response: for unexpected exception'() {
+        when: 'cm-handle response is created for an unexpected exception'
             def cmHandleRegistrationResponse =
                 CmHandleRegistrationResponse.createFailureResponse('cmHandle', new Exception('unexpected error'))
         then: 'the response is created with expected value'
@@ -51,18 +51,21 @@ class CmHandleRegistrationResponseSpec extends Specification {
             }
     }
 
-    def 'Failed Cm Handle Registration Response: for known error'() {
-        when: 'CMHandle response is created for known error'
+    def 'Failed cm-handle Registration Response: for #scenario'() {
+        when: 'cm-handle failure response is created for #scenario'
             def cmHandleRegistrationResponse =
-                CmHandleRegistrationResponse.createFailureResponse('cmHandle', RegistrationError.CM_HANDLE_ALREADY_EXIST)
+                CmHandleRegistrationResponse.createFailureResponse(cmHandleId, registrationError)
         then: 'the response is created with expected value'
             with(cmHandleRegistrationResponse) {
-                assert it.registrationError == RegistrationError.CM_HANDLE_ALREADY_EXIST
-                assert it.cmHandle == 'cmHandle'
+                assert it.registrationError == registrationError
+                assert it.cmHandle == cmHandleId
                 assert it.status == Status.FAILURE
-                assert errorText == RegistrationError.CM_HANDLE_ALREADY_EXIST.errorText
+                assert errorText == registrationError.errorText
             }
-
+        where:
+            scenario                   | cmHandleId  | registrationError
+            'cm-handle already exists' | 'cmHandle'  | RegistrationError.CM_HANDLE_ALREADY_EXIST
+            'cm-handle id is invalid'  | 'cm handle' | RegistrationError.CM_HANDLE_INVALID_ID
     }
 
 }
index a25f81e..5852c0c 100644 (file)
@@ -29,6 +29,8 @@ dataspaces:
     responses:
       '201':
         $ref: 'components.yml#/components/responses/Created'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
       '401':
         $ref: 'components.yml#/components/responses/Unauthorized'
       '403':
index 58a5ebf..41ad9ca 100755 (executable)
 
 package org.onap.cps.rest.controller
 
-import org.mapstruct.factory.Mappers
-
 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 
+import org.mapstruct.factory.Mappers
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
@@ -73,7 +72,7 @@ class AdminRestControllerSpec extends Specification {
 
     def 'Create new dataspace.'() {
         given: 'an endpoint'
-            def createDataspaceEndpoint = "$basePath/v1/dataspaces";
+            def createDataspaceEndpoint = "$basePath/v1/dataspaces"
         when: 'post is invoked'
             def response =
                     mvc.perform(
@@ -88,7 +87,7 @@ class AdminRestControllerSpec extends Specification {
 
     def 'Create dataspace over existing with same name.'() {
         given: 'an endpoint'
-            def createDataspaceEndpoint = "$basePath/v1/dataspaces";
+            def createDataspaceEndpoint = "$basePath/v1/dataspaces"
         and: 'the service method throws an exception indicating the dataspace is already defined'
             def thrownException = new AlreadyDefinedException(dataspaceName, new RuntimeException())
             mockCpsAdminService.createDataspace(dataspaceName) >> { throw thrownException }
index 44f7f77..2cb01ac 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020 Nordix Foundation
+ *  Copyright (C) 2020-2021 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  ================================================================================
index ecc9bf0..79d6e03 100644 (file)
@@ -23,7 +23,6 @@ package org.onap.cps.api;
 
 import java.util.Collection;
 import java.util.Map;
-import org.checkerframework.checker.nullness.qual.NonNull;
 import org.onap.cps.spi.CascadeDeleteAllowed;
 import org.onap.cps.spi.exceptions.DataInUseException;
 import org.onap.cps.spi.model.ModuleReference;
@@ -42,8 +41,8 @@ public interface CpsModuleService {
      * @param yangResourcesNameToContentMap yang resources (files) as a mep where key is resource name
      *                                      and value is content
      */
-    void createSchemaSet(@NonNull String dataspaceName, @NonNull String schemaSetName,
-                         @NonNull Map<String, String> yangResourcesNameToContentMap);
+    void createSchemaSet(String dataspaceName, String schemaSetName,
+                         Map<String, String> yangResourcesNameToContentMap);
 
     /**
      * Create a schema set from new modules and existing modules.
@@ -52,8 +51,8 @@ public interface CpsModuleService {
      * @param newModuleNameToContentMap YANG resources map where key is a module name and value is content
      * @param moduleReferences          List of YANG resources module references of the modules
      */
-    void createSchemaSetFromModules(@NonNull String dataspaceName, @NonNull String schemaSetName,
-                                    @NonNull Map<String, String> newModuleNameToContentMap,
+    void createSchemaSetFromModules(String dataspaceName, String schemaSetName,
+                                    Map<String, String> newModuleNameToContentMap,
                                     Collection<ModuleReference> moduleReferences);
 
     /**
@@ -63,7 +62,7 @@ public interface CpsModuleService {
      * @param schemaSetName schema set name
      * @return a SchemaSet
      */
-    SchemaSet getSchemaSet(@NonNull String dataspaceName, @NonNull String schemaSetName);
+    SchemaSet getSchemaSet(String dataspaceName, String schemaSetName);
 
     /**
      * Deletes Schema Set.
@@ -74,8 +73,8 @@ public interface CpsModuleService {
      * @throws DataInUseException if cascadeDeleteAllowed is set to CASCADE_DELETE_PROHIBITED and there
      *                           is associated anchor record exists in database
      */
-    void deleteSchemaSet(@NonNull String dataspaceName, @NonNull String schemaSetName,
-        @NonNull CascadeDeleteAllowed cascadeDeleteAllowed);
+    void deleteSchemaSet(String dataspaceName, String schemaSetName,
+                         CascadeDeleteAllowed cascadeDeleteAllowed);
 
     /**
      * Retrieve module references for the given dataspace name.
index beb0a15..68ae1eb 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020 Nordix Foundation
+ *  Copyright (C) 2020-2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
 package org.onap.cps.api;
 
 import java.util.Collection;
-import org.checkerframework.checker.nullness.qual.NonNull;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
 
@@ -40,7 +39,7 @@ public interface CpsQueryService {
      *                               included in the output
      * @return a collection of data nodes
      */
-    Collection<DataNode> queryDataNodes(@NonNull String dataspaceName, @NonNull String anchorName,
-        @NonNull String cpsPath, @NonNull FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> queryDataNodes(String dataspaceName, String anchorName,
+                                        String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
 }
index 1013add..7bec1e3 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020 Nordix Foundation
+ *  Copyright (C) 2020-2022 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  ================================================================================
@@ -30,6 +30,7 @@ import org.onap.cps.api.CpsAdminService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.spi.CpsAdminPersistenceService;
 import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.utils.CpsValidator;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
@@ -43,42 +44,50 @@ public class CpsAdminServiceImpl implements CpsAdminService {
 
     @Override
     public void createDataspace(final String dataspaceName) {
+        CpsValidator.validateNameCharacters(dataspaceName);
         cpsAdminPersistenceService.createDataspace(dataspaceName);
     }
 
     @Override
     public void deleteDataspace(final String dataspaceName) {
+        CpsValidator.validateNameCharacters(dataspaceName);
         cpsAdminPersistenceService.deleteDataspace(dataspaceName);
     }
 
     @Override
     public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName, anchorName);
         cpsAdminPersistenceService.createAnchor(dataspaceName, schemaSetName, anchorName);
     }
 
     @Override
     public Collection<Anchor> getAnchors(final String dataspaceName) {
+        CpsValidator.validateNameCharacters(dataspaceName);
         return cpsAdminPersistenceService.getAnchors(dataspaceName);
     }
 
     @Override
     public Collection<Anchor> getAnchors(final String dataspaceName, final String schemaSetName) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         return cpsAdminPersistenceService.getAnchors(dataspaceName, schemaSetName);
     }
 
     @Override
     public Anchor getAnchor(final String dataspaceName, final String anchorName) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         return cpsAdminPersistenceService.getAnchor(dataspaceName, anchorName);
     }
 
     @Override
     public void deleteAnchor(final String dataspaceName, final String anchorName) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         cpsDataService.deleteDataNodes(dataspaceName, anchorName, OffsetDateTime.now());
         cpsAdminPersistenceService.deleteAnchor(dataspaceName, anchorName);
     }
 
     @Override
     public Collection<String> queryAnchorNames(final String dataspaceName, final Collection<String> moduleNames) {
+        CpsValidator.validateNameCharacters(dataspaceName);
         final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames);
         return anchors.stream().map(Anchor::getName).collect(Collectors.toList());
     }
index 643614f..399457d 100755 (executable)
@@ -36,6 +36,7 @@ import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
+import org.onap.cps.utils.CpsValidator;
 import org.onap.cps.utils.YangUtils;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -56,6 +57,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void saveData(final String dataspaceName, final String anchorName, final String jsonData,
         final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final var dataNode = buildDataNode(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData);
         cpsDataPersistenceService.storeDataNode(dataspaceName, anchorName, dataNode);
         processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, ROOT_NODE_XPATH, Operation.CREATE);
@@ -64,6 +66,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
         final String jsonData, final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, parentNodeXpath, dataNode);
         processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.CREATE);
@@ -72,6 +75,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void saveListElements(final String dataspaceName, final String anchorName,
         final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Collection<DataNode> listElementDataNodeCollection =
             buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
@@ -82,12 +86,14 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
         final FetchDescendantsOption fetchDescendantsOption) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         return cpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption);
     }
 
     @Override
     public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
         final String jsonData, final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService
             .updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves());
@@ -102,6 +108,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         final Collection<DataNode> dataNodeUpdates =
             buildDataNodes(dataspaceName, anchorName,
                 parentNodeXpath, dataNodeUpdatesAsJson);
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
             processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate);
         }
@@ -122,6 +129,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void replaceNodeTree(final String dataspaceName, final String anchorName, final String parentNodeXpath,
         final String jsonData, final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName, dataNode);
         processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
@@ -130,6 +138,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
             final String jsonData, final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Collection<DataNode> newListElements =
                 buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
         replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
@@ -138,6 +147,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
             final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
         processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
     }
@@ -145,6 +155,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
                                final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
         processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, dataNodeXpath, Operation.DELETE);
     }
@@ -152,6 +163,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void deleteDataNodes(final String dataspaceName, final String anchorName,
         final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
         processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp);
@@ -160,6 +172,7 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Override
     public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
         final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
         processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, listNodeXpath, Operation.DELETE);
     }
index f0e79c6..8e43227 100644 (file)
@@ -33,6 +33,7 @@ import org.onap.cps.spi.exceptions.SchemaSetInUseException;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.ModuleReference;
 import org.onap.cps.spi.model.SchemaSet;
+import org.onap.cps.utils.CpsValidator;
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -48,6 +49,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Override
     public void createSchemaSet(final String dataspaceName, final String schemaSetName,
         final Map<String, String> yangResourcesNameToContentMap) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         final var yangTextSchemaSourceSet
             = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap);
         cpsModulePersistenceService.storeSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap);
@@ -58,6 +60,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     public void createSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
         final Map<String, String> newModuleNameToContentMap,
         final Collection<ModuleReference> moduleReferences) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         cpsModulePersistenceService.storeSchemaSetFromModules(dataspaceName, schemaSetName,
             newModuleNameToContentMap, moduleReferences);
 
@@ -65,6 +68,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
 
     @Override
     public SchemaSet getSchemaSet(final String dataspaceName, final String schemaSetName) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         final var yangTextSchemaSourceSet = yangTextSchemaSourceSetCache
             .get(dataspaceName, schemaSetName);
         return SchemaSet.builder().name(schemaSetName).dataspaceName(dataspaceName)
@@ -75,6 +79,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Transactional
     public void deleteSchemaSet(final String dataspaceName, final String schemaSetName,
         final CascadeDeleteAllowed cascadeDeleteAllowed) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName, schemaSetName);
         if (!anchors.isEmpty() && isCascadeDeleteProhibited(cascadeDeleteAllowed)) {
             throw new SchemaSetInUseException(dataspaceName, schemaSetName);
@@ -89,12 +94,14 @@ public class CpsModuleServiceImpl implements CpsModuleService {
 
     @Override
     public Collection<ModuleReference> getYangResourceModuleReferences(final String dataspaceName) {
+        CpsValidator.validateNameCharacters(dataspaceName);
         return cpsModulePersistenceService.getYangResourceModuleReferences(dataspaceName);
     }
 
     @Override
     public Collection<ModuleReference> getYangResourcesModuleReferences(final String dataspaceName,
         final String anchorName) {
+        CpsValidator.validateNameCharacters(dataspaceName);
         return cpsModulePersistenceService.getYangResourceModuleReferences(dataspaceName, anchorName);
     }
 
index dd9f160..c2003d6 100644 (file)
@@ -25,6 +25,7 @@ import org.onap.cps.api.CpsQueryService;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.utils.CpsValidator;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -37,6 +38,7 @@ public class CpsQueryServiceImpl implements CpsQueryService {
     @Override
     public Collection<DataNode> queryDataNodes(final String dataspaceName, final String anchorName,
         final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         return cpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
     }
 }
index 03b52a3..fb881a9 100644 (file)
@@ -24,6 +24,7 @@ package org.onap.cps.api.impl;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.util.Map;
 import org.onap.cps.spi.CpsModulePersistenceService;
+import org.onap.cps.utils.CpsValidator;
 import org.onap.cps.yang.YangTextSchemaSourceSet;
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -52,6 +53,7 @@ public class YangTextSchemaSourceSetCache {
      */
     @Cacheable(key = "#p0.concat('-').concat(#p1)")
     public YangTextSchemaSourceSet get(final String dataspaceName, final String schemaSetName) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         final Map<String, String> yangResourceNameToContent =
                 cpsModulePersistenceService.getYangSchemaResources(dataspaceName, schemaSetName);
         return YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent);
@@ -69,6 +71,7 @@ public class YangTextSchemaSourceSetCache {
     @CanIgnoreReturnValue
     public YangTextSchemaSourceSet updateCache(final String dataspaceName, final String schemaSetName,
             final YangTextSchemaSourceSet yangTextSchemaSourceSet) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         return yangTextSchemaSourceSet;
     }
 
@@ -81,6 +84,7 @@ public class YangTextSchemaSourceSetCache {
      */
     @CacheEvict(key = "#p0.concat('-').concat(#p1)")
     public void removeFromCache(final String dataspaceName, final String schemaSetName) {
+        CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         // Spring provides implementation for removing object from cache
     }
 
diff --git a/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java
new file mode 100644 (file)
index 0000000..dd16495
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.utils;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.exceptions.DataValidationException;
+
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class CpsValidator {
+
+    private static final char[] UNSUPPORTED_NAME_CHARACTERS = "!\" #$%&'()*+,./\\:;<=>?@[]^`{|}~".toCharArray();
+
+    /**
+     * Validate characters in names within cps.
+     * @param names names of data to be validated
+     */
+    public static void validateNameCharacters(final String... names) {
+        for (final String name : names) {
+            final  Collection<Character> charactersOfName = Lists.charactersOf(name);
+            for (final char unsupportedCharacter : UNSUPPORTED_NAME_CHARACTERS) {
+                if (charactersOfName.contains(unsupportedCharacter)) {
+                    throw new DataValidationException("Name or ID Validation Error.",
+                        name + " invalid token encountered at position " + (name.indexOf(unsupportedCharacter) + 1));
+                }
+            }
+        }
+    }
+}
index eb06199..fc1293c 100644 (file)
@@ -50,9 +50,9 @@ class CpsDataServiceImplSpec extends Specification {
         mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
     }
 
-    def dataspaceName = 'some dataspace'
-    def anchorName = 'some anchor'
-    def schemaSetName = 'some schema set'
+    def dataspaceName = 'some-dataspace'
+    def anchorName = 'some-anchor'
+    def schemaSetName = 'some-schema-set'
     def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
     def observedTimestamp = OffsetDateTime.now()
 
index 4878f4c..aa01b44 100644 (file)
@@ -35,8 +35,8 @@ class CpsQueryServiceImplSpec extends Specification {
 
     def 'Query data nodes by cps path with #fetchDescendantsOption.'() {
         given: 'a dataspace name, an anchor name and a cps path'
-            def dataspaceName = 'some dataspace'
-            def anchorName = 'some anchor'
+            def dataspaceName = 'some-dataspace'
+            def anchorName = 'some-anchor'
             def cpsPath = '/cps-path'
         when: 'queryDataNodes is invoked'
             objectUnderTest.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption)
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy
new file mode 100644 (file)
index 0000000..191472c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.utils
+
+import org.onap.cps.spi.exceptions.DataValidationException
+import spock.lang.Specification
+
+class CpsValidatorSpec extends Specification {
+
+
+    def 'Validating a valid string.'() {
+        when: 'the string is validated using a valid name'
+            CpsValidator.validateNameCharacters('name-with-no-spaces')
+        then: 'no exception is thrown'
+            noExceptionThrown()
+    }
+
+    def 'Validating an invalid string.'() {
+        when: 'the string is validated using an invalid name'
+            CpsValidator.validateNameCharacters(name)
+        then: 'a data validation exception is thrown'
+            def exceptionThrown = thrown(DataValidationException)
+        and: 'the error was encountered at the following index in #scenario'
+            assert exceptionThrown.getDetails().contains(expectedErrorMessage)
+        where: 'the following names are used'
+            scenario     | name               || expectedErrorMessage
+            'position 5' | 'name with spaces' || 'name with spaces invalid token encountered at position 5'
+            'position 9' | 'nameWith Space'   || 'nameWith Space invalid token encountered at position 9'
+    }
+}