diff options
23 files changed, 884 insertions, 406 deletions
diff --git a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java index 67af6f10a9..ac5337dbd3 100644 --- a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java +++ b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java @@ -30,10 +30,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; import org.onap.cps.ncmp.rest.model.RestModuleDefinition; import org.onap.cps.ncmp.rest.model.RestModuleReference; @@ -58,20 +57,53 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { private String pathToResponseFiles; @Override - public ResponseEntity<Void> createResourceDataRunningForCmHandle(@NotNull @Valid final String resourceIdentifier, - final String cmHandleId, @Valid final Object body, final String contentType) { + public ResponseEntity<Object> getResourceDataForCmHandle(final String dataStoreType, + final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { + if (DatastoreType.PASSTHROUGH_OPERATIONAL == DatastoreType.fromDatastoreName(dataStoreType)) { + final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery); + final Map<String, Object> asyncResponseData = asyncResponse.getBody(); + Object responseObject = null; + // read JSON file and map/convert to java POJO + final ClassPathResource resource = + new ClassPathResource(pathToResponseFiles + "passthrough-operational-example.json"); + try (InputStream inputStream = resource.getInputStream()) { + final String string = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + final ObjectMapper mapper = new ObjectMapper(); + responseObject = mapper.readValue(string, Object.class); + } catch (final IOException exception) { + log.error("Error reading the file.", exception); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + if (asyncResponseData == null) { + return ResponseEntity.ok(responseObject); + } + return ResponseEntity.ok(asyncResponse); + } + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier, + final String cmHandleId, + final Object body, + final String contentType) { return new ResponseEntity<>(HttpStatus.CREATED); } @Override public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String cmHandleId, - @NotNull @Valid final String resourceIdentifier, final String contentType) { + final String resourceIdentifier, + final String contentType) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Override public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles( - final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryParameters cmHandleQueryParameters) { List<RestOutputCmHandle> restOutputCmHandles = null; // read JSON file and map/convert to java POJO final ClassPathResource resource = new ClassPathResource(pathToResponseFiles + "cmHandlesSearch.json"); @@ -88,19 +120,19 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { @Override public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId, - final Boolean dataSyncEnabled) { + final Boolean dataSyncEnabled) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @Override public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryParameters cmHandleQueryParameters) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @Override public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @@ -119,48 +151,11 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } - /** - * Get resource data from operational datastore. - * - * @param cmHandleId cm handle identifier - * @param resourceIdentifier resource identifier - * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter - * @return {@code ResponseEntity} response from dmi plugin - */ - @Override - public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandleId, - final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery) { - final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery); - final Map<String, Object> asyncResponseData = asyncResponse.getBody(); - Object responseObject = null; - // read JSON file and map/convert to java POJO - final ClassPathResource resource = new ClassPathResource(pathToResponseFiles - + "passthrough-operational-example.json"); - try (InputStream inputStream = resource.getInputStream()) { - final String string = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - final ObjectMapper mapper = new ObjectMapper(); - responseObject = mapper.readValue(string, Object.class); - } catch (final IOException exception) { - log.error("Error reading the file.", exception); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); - } - if (asyncResponseData == null) { - return ResponseEntity.ok(responseObject); - } - return ResponseEntity.ok(asyncResponse); - - } - - @Override - public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandleId, - final String resourceIdentifier, final String options, final String topic) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - @Override public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandleId, final Object body, final String contentType) { + final String cmHandleId, + final Object body, + final String contentType) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @@ -170,8 +165,10 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { } @Override - public ResponseEntity<Object> updateResourceDataRunningForCmHandle(@NotNull @Valid final String resourceIdentifier, - final String cmHandleId, @Valid final Object body, final String contentType) { + public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String resourceIdentifier, + final String cmHandleId, + final Object body, + final String contentType) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 427f083698..7ca09ceedb 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -86,7 +86,7 @@ components: type: array items: type: string - example: [my-cm-handle1, my-cm-handle2, my-cm-handle3] + example: [ my-cm-handle1, my-cm-handle2, my-cm-handle3 ] DmiPluginRegistrationErrorResponse: type: object properties: @@ -124,14 +124,14 @@ components: type: string example: my-cm-handle cmHandleProperties: - $ref: '#/components/schemas/RestCmHandleProperties' + $ref: '#/components/schemas/RestCmHandleProperties' publicCmHandleProperties: - $ref: '#/components/schemas/RestCmHandleProperties' + $ref: '#/components/schemas/RestCmHandleProperties' RestCmHandleProperties: - type: object - additionalProperties: - type: string - example: my-property + type: object + additionalProperties: + type: string + example: my-property #Response Schemas RestModuleReference: @@ -288,21 +288,21 @@ components: examples: dataSampleRequest: - summary: Sample request - description: Sample request body - value: - test:bookstore: - bookstore-name: Chapters - categories: - - code: '01' - name: SciFi - books: + summary: Sample request + description: Sample request body + value: + test:bookstore: + bookstore-name: Chapters + categories: + - code: '01' + name: SciFi + books: - authors: - Iain M. Banks - Ursula K. Le Guin - - code: '02' - name: kids - books: + - code: '02' + name: kids + books: - authors: - Philip Pullman @@ -351,22 +351,22 @@ components: - Philip Pullman dataSampleResponse: - summary: Sample response - description: Sample response for selecting 'sample 1'. - value: - bookstore: - categories: - - code: '01' - books: - - authors: - - Iain M. Banks - - Ursula K. Le Guin - name: SciFi - - code: '02' - books: - - authors: - - Philip Pullman - name: kids + summary: Sample response + description: Sample response for selecting 'sample 1'. + value: + bookstore: + categories: + - code: '01' + books: + - authors: + - Iain M. Banks + - Ursula K. Le Guin + name: SciFi + - code: '02' + books: + - authors: + - Philip Pullman + name: kids allCmHandleQueryParameters: value: @@ -448,7 +448,7 @@ components: includeDescendantsOptionInQuery: name: include-descendants in: query - description: include-descendants + description: Determines if descendants are included in response required: false schema: type: boolean @@ -526,6 +526,14 @@ components: type: string default: application/json example: application/yang-data+json + datastoreName: + name: ncmp-datastore-name + in: path + description: The type of the requested data + required: true + schema: + type: string + example: ncmp-datastore:operational responses: NotFound: @@ -555,9 +563,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 403 - message: Forbidden error message - details: Forbidden error details + status: 403 + message: Forbidden error message + details: Forbidden error details BadRequest: description: Bad Request content: @@ -565,9 +573,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 400 BAD_REQUEST - message: Bad request error message - details: Bad request error details + status: 400 BAD_REQUEST + message: Bad request error message + details: Bad request error details Conflict: description: Conflict content: @@ -575,9 +583,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 409 CONFLICT - message: Conflict error message - details: Conflict error details + status: 409 CONFLICT + message: Conflict error message + details: Conflict error details NotImplemented: description: The given path has not been implemented content: @@ -585,9 +593,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 501 - message: Not implemented error message - details: Not implemented error details + status: 501 + message: Not implemented error message + details: Not implemented error details Ok: description: OK content: @@ -596,10 +604,10 @@ components: type: object Created: description: Created - content: {} + content: { } NoContent: description: No Content - content: {} + content: { } InternalServerError: description: Internal Server Error content: diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index 4266fc4152..5e22f773aa 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -17,18 +17,21 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END========================================================= -getResourceDataForPassthroughOperational: + +getResourceDataForCmHandle: get: tags: - network-cm-proxy - summary: Get resource data from pass-through operational for cm handle - description: Get resource data from pass-through operational for given cm handle - operationId: getResourceDataOperationalForCmHandle + summary: Get resource data for cm handle + description: Get resource data for given cm handle + operationId: getResourceDataForCmHandle parameters: + - $ref: 'components.yaml#/components/parameters/datastoreName' - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' - $ref: 'components.yaml#/components/parameters/topicParamInQuery' + - $ref: 'components.yaml#/components/parameters/includeDescendantsOptionInQuery' responses: 200: description: OK @@ -51,37 +54,6 @@ getResourceDataForPassthroughOperational: $ref: 'components.yaml#/components/responses/BadGateway' resourceDataForPassthroughRunning: - get: - tags: - - network-cm-proxy - summary: Get resource data from pass-through running for cm handle - description: Get resource data from pass-through running for given cm handle - operationId: getResourceDataRunningForCmHandle - parameters: - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' - - $ref: 'components.yaml#/components/parameters/topicParamInQuery' - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - examples: - dataSampleResponse: - $ref: 'components.yaml#/components/examples/dataSampleResponse' - 400: - $ref: 'components.yaml#/components/responses/BadRequest' - 401: - $ref: 'components.yaml#/components/responses/Unauthorized' - 403: - $ref: 'components.yaml#/components/responses/Forbidden' - 500: - $ref: 'components.yaml#/components/responses/InternalServerError' - 502: - $ref: 'components.yaml#/components/responses/BadGateway' post: tags: - network-cm-proxy diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index 8e020668a0..ed15fcd628 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -26,8 +26,8 @@ info: servers: - url: /ncmp paths: - /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-operational: - $ref: 'ncmp.yml#/getResourceDataForPassthroughOperational' + /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}: + $ref: 'ncmp.yml#/getResourceDataForCmHandle' /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-running: $ref: 'ncmp.yml#/resourceDataForPassthroughRunning' diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index 6a700c3e12..b3021d2410 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -185,7 +185,8 @@ <goal>copy-resources</goal> </goals> <configuration> - <outputDirectory>${project.basedir}/target/classes/static/api-docs/cps-ncmp</outputDirectory> + <outputDirectory>${project.basedir}/target/classes/static/api-docs/cps-ncmp + </outputDirectory> <resources> <resource> <directory>${project.basedir}/target/generated-sources/swagger/</directory> diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index d2ed393794..9aa8263fc5 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -29,21 +29,18 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.stream.Collectors; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; -import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException; import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType; +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandler; +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory; import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper; import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; @@ -53,9 +50,7 @@ import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState; import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties; import org.onap.cps.ncmp.rest.util.DeprecationHelper; -import org.onap.cps.utils.CpsValidator; import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -68,94 +63,50 @@ import org.springframework.web.bind.annotation.RestController; public class NetworkCmProxyController implements NetworkCmProxyApi { private static final String NO_BODY = null; - private static final String NO_REQUEST_ID = null; - private static final String NO_TOPIC = null; private final NetworkCmProxyDataService networkCmProxyDataService; private final JsonObjectMapper jsonObjectMapper; - private final DeprecationHelper deprecationHelper; private final NcmpRestInputMapper ncmpRestInputMapper; private final CmHandleStateMapper cmHandleStateMapper; - private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; - @Value("${notification.async.executor.time-out-value-in-ms:2000}") - private int timeOutInMilliSeconds; - @Value("${notification.enabled:true}") - private boolean asyncEnabled; + private final NcmpDatastoreResourceRequestHandlerFactory ncmpDatastoreResourceRequestHandlerFactory; /** - * Get resource data from operational datastore. + * Get resource data from datastore. * - * @param cmHandle cm handle identifier - * @param resourceIdentifier resource identifier + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param resourceIdentifier resource identifier * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter + * @param topicParamInQuery topic query parameter + * @param includeDescendants whether include descendants * @return {@code ResponseEntity} response from dmi plugin */ - @Override - public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandle, - final @NotNull @Valid String resourceIdentifier, - final @Valid String optionsParamInQuery, - final @Valid String topicParamInQuery) { - if (asyncEnabled && isValidTopic(topicParamInQuery)) { - final String requestId = UUID.randomUUID().toString(); - log.info("Received Async passthrough-operational request with id {}", requestId); - cpsNcmpTaskExecutor.executeTask(() -> - networkCmProxyDataService.getResourceDataOperationalForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId - ), timeOutInMilliSeconds - ); - return ResponseEntity.ok(Map.of("requestId", requestId)); - } else { - log.warn("Asynchronous messaging is currently disabled for passthrough-operational." - + " Will use synchronous operation."); - } - final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID); - - return ResponseEntity.ok(responseObject); - } - - /** - * Get resource data from pass-through running datastore. - * - * @param cmHandle cm handle identifier - * @param resourceIdentifier resource identifier - * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter - * @return {@code ResponseEntity} response from dmi plugin - */ @Override - public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandle, - final @NotNull @Valid String resourceIdentifier, - final @Valid String optionsParamInQuery, - final @Valid String topicParamInQuery) { - if (asyncEnabled && isValidTopic(topicParamInQuery)) { - final String requestId = UUID.randomUUID().toString(); - log.info("Received Async passthrough-running request with id {}", requestId); - cpsNcmpTaskExecutor.executeTask(() -> - networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId - ), timeOutInMilliSeconds - ); - return ResponseEntity.ok(Map.of("requestId", requestId)); - } else { - log.warn("Asynchronous messaging is currently disabled for passthrough-running." - + " Will use synchronous operation."); - } + public ResponseEntity<Object> getResourceDataForCmHandle(final String datastoreName, + final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { - final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID); + final NcmpDatastoreResourceRequestHandler ncmpDatastoreResourceRequestHandler = + ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.fromDatastoreName(datastoreName)); - return ResponseEntity.ok(responseObject); + return ncmpDatastoreResourceRequestHandler.getResourceData(cmHandle, resourceIdentifier, + optionsParamInQuery, topicParamInQuery, includeDescendants); } @Override public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandle, - final Object requestBody, final String contentType) { - final Object responseObject = networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceIdentifier, PATCH, jsonObjectMapper.asJsonString(requestBody), contentType); + final String cmHandle, + final Object requestBody, + final String contentType) { + final Object responseObject = networkCmProxyDataService + .writeResourceDataPassThroughRunningForCmHandle( + cmHandle, resourceIdentifier, PATCH, + jsonObjectMapper.asJsonString(requestBody), contentType); return ResponseEntity.ok(responseObject); } @@ -163,14 +114,16 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Create resource data in datastore pass-through running for given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param requestBody the request body - * @param contentType content type of body + * @param cmHandle cm handle identifier + * @param requestBody the request body + * @param contentType content type of body * @return {@code ResponseEntity} response from dmi plugin */ @Override public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandle, final Object requestBody, final String contentType) { + final String cmHandle, + final Object requestBody, + final String contentType) { networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType); return new ResponseEntity<>(HttpStatus.CREATED); @@ -180,9 +133,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Update resource data in datastore pass-through running for given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param requestBody the request body - * @param contentType content type of the body + * @param cmHandle cm handle identifier + * @param requestBody the request body + * @param contentType content type of the body * @return response entity */ @Override @@ -197,11 +150,11 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** - * Delete resource data in datastore pass-through running for a given cm-handle. + * Delete resource data in datastore pass-through running for a given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param contentType content type of the body + * @param cmHandle cm handle identifier + * @param contentType content type of the body * @return response entity no content if request is successful */ @Override @@ -209,7 +162,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final String resourceIdentifier, final String contentType) { networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceIdentifier, DELETE, NO_BODY, contentType); + resourceIdentifier, DELETE, NO_BODY, contentType); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -240,7 +193,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ @Override public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryParameters cmHandleQueryParameters) { final CmHandleQueryApiParameters cmHandleQueryApiParameters = jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class); final Set<String> cmHandleIds = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters); @@ -249,6 +202,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Search for Cm Handle and Properties by Name. + * * @param cmHandleId cm-handle identifier * @return cm handle and its properties */ @@ -261,33 +215,35 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Get Cm Handle Properties by Cm Handle Id. + * * @param cmHandleId cm-handle identifier * @return cm handle properties */ @Override public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties(); cmHandlePublicProperties.add(networkCmProxyDataService.getCmHandlePublicProperties(cmHandleId)); final RestOutputCmHandlePublicProperties restOutputCmHandlePublicProperties = - new RestOutputCmHandlePublicProperties(); + new RestOutputCmHandlePublicProperties(); restOutputCmHandlePublicProperties.setPublicCmHandleProperties(cmHandlePublicProperties); return ResponseEntity.ok(restOutputCmHandlePublicProperties); } /** * Get Cm Handle State by Cm Handle Id. + * * @param cmHandleId cm-handle identifier * @return cm handle state */ @Override public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { final CompositeState cmHandleState = networkCmProxyDataService.getCmHandleCompositeState(cmHandleId); final RestOutputCmHandleCompositeState restOutputCmHandleCompositeState = - new RestOutputCmHandleCompositeState(); + new RestOutputCmHandleCompositeState(); restOutputCmHandleCompositeState.setState( - cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState)); + cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState)); return ResponseEntity.ok(restOutputCmHandleCompositeState); } @@ -314,22 +270,22 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) { final List<RestModuleReference> restModuleReferences = - networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream() - .map(ncmpRestInputMapper::toRestModuleReference) - .collect(Collectors.toList()); + networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream() + .map(ncmpRestInputMapper::toRestModuleReference) + .collect(Collectors.toList()); return new ResponseEntity<>(restModuleReferences, HttpStatus.OK); } /** * Set the data sync enabled flag, along with the data sync state for the specified cm handle. * - * @param cmHandleId cm handle id + * @param cmHandleId cm handle id * @param dataSyncEnabledFlag data sync enabled flag * @return response entity ok if request is successful */ @Override public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId, - final Boolean dataSyncEnabledFlag) { + final Boolean dataSyncEnabledFlag) { networkCmProxyDataService.setDataSyncEnabled(cmHandleId, dataSyncEnabledFlag); return new ResponseEntity<>(HttpStatus.OK); } @@ -345,15 +301,6 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { return restOutputCmHandle; } - private static boolean isValidTopic(final String topicName) { - if (topicName == null) { - return false; - } - if (CpsValidator.validateTopicName(topicName)) { - return true; - } - throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic"); - } } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java new file mode 100644 index 0000000000..959c85d141 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java @@ -0,0 +1,53 @@ +/* + * ============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.ncmp.rest.controller.handlers; + + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; + +@Getter +public enum DatastoreType { + + OPERATIONAL("ncmp-datastore:operational"), + PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"), + PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"); + + DatastoreType(final String datastoreName) { + this.datastoreName = datastoreName; + } + + private final String datastoreName; + private static final Map<String, DatastoreType> datastoreNameToDatastoreType = new HashMap<>(); + + static { + Arrays.stream(DatastoreType.values()).forEach( + type -> datastoreNameToDatastoreType.put(type.getDatastoreName(), type)); + } + + public static DatastoreType fromDatastoreName(final String datastoreName) { + return datastoreNameToDatastoreType.get(datastoreName); + } + +} + diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java new file mode 100644 index 0000000000..6ed9b8c4d1 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java @@ -0,0 +1,55 @@ +/* + * ============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.ncmp.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.spi.FetchDescendantsOption; + +@Slf4j +public class NcmpDatastoreOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastoreOperationalResourceRequestHandler(final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + final FetchDescendantsOption fetchDescendantsOption = + Boolean.TRUE.equals(includeDescendant) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS + : FetchDescendantsOption.OMIT_DESCENDANTS; + + return () -> networkCmProxyDataService.getResourceDataOperational(cmHandle, resourceIdentifier, + fetchDescendantsOption); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java new file mode 100644 index 0000000000..196e5bd33d --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java @@ -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.ncmp.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; + +@Slf4j +public class NcmpDatastorePassthroughOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastorePassthroughOperationalResourceRequestHandler( + final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + return () -> networkCmProxyDataService.getResourceDataOperationalForCmHandle( + cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java new file mode 100644 index 0000000000..5bf16b7499 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java @@ -0,0 +1,50 @@ +/* + * ============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.ncmp.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; + +@Slf4j +public class NcmpDatastorePassthroughRunningResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastorePassthroughRunningResourceRequestHandler( + final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + return () -> networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( + cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java new file mode 100644 index 0000000000..a6d313b05f --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java @@ -0,0 +1,92 @@ +/* + * ============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.ncmp.rest.controller.handlers; + +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.ncmp.rest.util.TopicValidator; +import org.springframework.http.ResponseEntity; + +@RequiredArgsConstructor +@Slf4j +public abstract class NcmpDatastoreResourceRequestHandler { + + private static final String NO_REQUEST_ID = null; + private static final String NO_TOPIC = null; + + protected final NetworkCmProxyDataService networkCmProxyDataService; + protected final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; + protected final int timeOutInMilliSeconds; + protected final boolean notificationFeatureEnabled; + + protected abstract Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant); + + + /** + * Get resource data from datastore. + * + * @param cmHandleId the cm handle + * @param resourceIdentifier the resource identifier + * @param optionsParamInQuery the options param in query + * @param topicParamInQuery the topic param in query + * @param includeDescendants whether include descendants + * @return the response entity + */ + public ResponseEntity<Object> getResourceData(final String cmHandleId, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { + + final String requestId = UUID.randomUUID().toString(); + final boolean asyncResponseRequested = topicParamInQuery != null; + + if (asyncResponseRequested && notificationFeatureEnabled) { + TopicValidator.validateTopicName(topicParamInQuery); + log.debug("Received Async request with id {}", requestId); + cpsNcmpTaskExecutor.executeTask( + getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, + includeDescendants), timeOutInMilliSeconds); + + return ResponseEntity.ok(Map.of("requestId", requestId)); + } + + if (asyncResponseRequested) { + log.warn("Asynchronous messaging is currently disabled, will use synchronous operation."); + } + + final Object responseObject = + getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID, + includeDescendants).get(); + + return ResponseEntity.ok(responseObject); + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java new file mode 100644 index 0000000000..35bd578ce2 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java @@ -0,0 +1,64 @@ +/* + * ============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.ncmp.rest.controller.handlers; + +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class NcmpDatastoreResourceRequestHandlerFactory { + + private final NetworkCmProxyDataService networkCmProxyDataService; + private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; + + @Value("${notification.async.executor.time-out-value-in-ms:2000}") + private int timeOutInMilliSeconds; + @Value("${notification.enabled:true}") + private boolean notificationFeatureEnabled; + + /** + * Gets ncmp datastore handler. + * + * @param datastoreType the datastore type + * @return the ncmp datastore handler + */ + public NcmpDatastoreResourceRequestHandler getNcmpDatastoreResourceRequestHandler( + final DatastoreType datastoreType) { + + switch (datastoreType) { + case OPERATIONAL: + return new NcmpDatastoreOperationalResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + case PASSTHROUGH_RUNNING: + return new NcmpDatastorePassthroughRunningResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + case PASSTHROUGH_OPERATIONAL: + return new NcmpDatastorePassthroughOperationalResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + default: + return null; + } + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java index b56ca7b8c2..6a52d5861e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.exception; +package org.onap.cps.ncmp.rest.exceptions; import lombok.Getter; diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java index c72373344d..98d7f6fd1d 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java @@ -25,7 +25,6 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.exception.DmiRequestException; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; -import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException; import org.onap.cps.ncmp.api.impl.exception.NcmpException; import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException; import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController; diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java new file mode 100644 index 0000000000..313e7bc012 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java @@ -0,0 +1,44 @@ +/* + * ============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.ncmp.rest.util; + +import java.util.regex.Pattern; +import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException; + +public class TopicValidator { + + private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|" + + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$"); + + /** + * Validate kafka topic name pattern. + * + * @param topicName name of the topic to be validated + * + * @throws InvalidTopicException if the topic is not valid + */ + public static void validateTopicName(final String topicName) { + if (!TOPIC_NAME_PATTERN.matcher(topicName).matches()) { + throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic"); + } + } + +} diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index d568a5a925..6e461fa59e 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -23,51 +23,59 @@ package org.onap.cps.ncmp.rest.controller +import com.fasterxml.jackson.databind.ObjectMapper import org.mapstruct.factory.Mappers +import org.onap.cps.TestUtils +import org.onap.cps.ncmp.api.NetworkCmProxyDataService import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState -import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughOperationalResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughRunningResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper +import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.ModuleDefinition +import org.onap.cps.spi.model.ModuleReference +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 +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc import spock.lang.Shared +import spock.lang.Specification import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational -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.patch -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.TestUtils -import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.utils.JsonObjectMapper -import org.onap.cps.ncmp.api.NetworkCmProxyDataService -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 -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.servlet.MockMvc -import spock.lang.Specification +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete @WebMvcTest(NetworkCmProxyController) class NetworkCmProxyControllerSpec extends Specification { + public static final int TIMEOUT_IN_MS = 2000 + public static final boolean NOTIFICATION_ENABLED = true + @Autowired MockMvc mvc @@ -92,6 +100,9 @@ class NetworkCmProxyControllerSpec extends Specification { @SpringBean DeprecationHelper stubbedDeprecationHelper = Stub() + @SpringBean + NcmpDatastoreResourceRequestHandlerFactory stubbedNcmpDatastoreResourceRequestHandlerFactory = Stub() + @Value('${rest.api.ncmp-base-path}/v1') def ncmpBasePathV1 @@ -104,21 +115,38 @@ class NetworkCmProxyControllerSpec extends Specification { def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + void setup() { + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.OPERATIONAL) >> + new NcmpDatastoreOperationalResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.PASSTHROUGH_OPERATIONAL) >> + new NcmpDatastorePassthroughOperationalResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.PASSTHROUGH_RUNNING) >> + new NcmpDatastorePassthroughRunningResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + } + def 'Get Resource Data from pass-through operational.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)" + "?resourceIdentifier=parent/child&options=(a=1,b=2)" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle' 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle', - 'parent/child', - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) + 'parent/child', + '(a=1,b=2)', + NO_TOPIC, + NO_REQUEST_ID) and: 'response status is Ok' response.status == HttpStatus.OK.value() } @@ -126,22 +154,22 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get Resource Data from #datastoreInUrl with #scenario.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}" + "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response + get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response then: 'task executor is called appropriate number of times' - expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, 2000) + expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, TIMEOUT_IN_MS) and: 'response status is expected' response.status == HttpStatus.OK.value() where: 'the following parameters are used' - scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions - 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1 - 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0 - 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1 - 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1 - 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0 - 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1 + scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions + 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1 + 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0 + 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1 + 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1 + 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0 + 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1 } def 'Fail to get Resource Data from #datastoreInUrl when #scenario.'() { @@ -168,17 +196,17 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)" + "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)" and: 'ncmp service returns json object' mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle', - resourceIdentifier, - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) >> '{valid-json}' + resourceIdentifier, + '(a=1,b=2)', + NO_TOPIC, + NO_REQUEST_ID) >> '{valid-json}' when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'response status is Ok' response.status == HttpStatus.OK.value() @@ -194,7 +222,7 @@ class NetworkCmProxyControllerSpec extends Specification { '? needs to be encoded as %3F' | 'idWith%3F' } - def 'Update resource data from pass-through running.' () { + def 'Update resource data from pass-through running.'() { given: 'update resource data url' def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + "?resourceIdentifier=parent/child" @@ -210,15 +238,15 @@ class NetworkCmProxyControllerSpec extends Specification { response.status == HttpStatus.OK.value() } - def 'Create Resource Data from pass-through running with #scenario.' () { + def 'Create Resource Data from pass-through running with #scenario.'() { given: 'resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" def requestBody = '{"some-key":"some-value"}' when: 'create resource request is performed' def response = mvc.perform( - post(url) - .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) + post(url) + .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) ).andReturn().response then: 'ncmp service method to create resource called' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', @@ -227,14 +255,14 @@ class NetworkCmProxyControllerSpec extends Specification { response.status == HttpStatus.CREATED.value() } - def 'Get module references for the given dataspace and cm handle.' () { + def 'Get module references for the given dataspace and cm handle.'() { given: 'get module references url' def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules" when: 'get module resource request is performed' - def response =mvc.perform(get(getUrl)).andReturn().response + def response = mvc.perform(get(getUrl)).andReturn().response then: 'ncmp service method to get yang resource module references is called' mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle') - >> [new ModuleReference(moduleName: 'some-name1',revision: '2021-10-03')] + >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')] and: 'response contains an array with the module name and revision' response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]' and: 'response returns an OK http code' @@ -248,15 +276,15 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns two cm handles' def cmHandle1 = new NcmpServiceCmHandle() cmHandle1.cmHandleId = 'some-cmhandle-id1' - cmHandle1.publicProperties = [color:'yellow'] + cmHandle1.publicProperties = [color: 'yellow'] def cmHandle2 = new NcmpServiceCmHandle() cmHandle2.cmHandleId = 'some-cmhandle-id2' - cmHandle2.publicProperties = [color:'green'] + cmHandle2.publicProperties = [color: 'green'] mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2] when: 'the searches api is invoked' def response = mvc.perform(post(searchesEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .content(jsonString)).andReturn().response + .contentType(MediaType.APPLICATION_JSON) + .content(jsonString)).andReturn().response then: 'response status returns OK' response.status == HttpStatus.OK.value() and: 'the expected response content is returned' @@ -268,14 +296,15 @@ class NetworkCmProxyControllerSpec extends Specification { def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle" and: 'an existing ncmp service cm handle' def cmHandleId = 'some-cm-handle' - def dmiProperties = [ prop:'some DMI property' ] - def publicProperties = [ "public prop":'some public property' ] + def dmiProperties = [prop: 'some DMI property'] + def publicProperties = ["public prop": 'some public property'] def compositeState = compositeStateTestObject() def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) and: 'the service method is invoked with the cm handle id' 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle when: 'the cm handle details api is invoked' - def response = mvc.perform(get(cmHandleDetailsEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandleDetailsEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the public properties' @@ -286,30 +315,34 @@ class NetworkCmProxyControllerSpec extends Specification { !response.contentAsString.contains("some DMI property") } - def 'Get Cm Handle public properties by Cm Handle id.' () { + def 'Get Cm Handle public properties by Cm Handle id.'() { given: 'a cm handle properties endpoint' def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties" and: 'some cm handle public properties' - def publicProperties = [ 'public prop':'some public property' ] + def publicProperties = ['public prop': 'some public property'] and: 'the service method is invoked with the cm handle id returning the cm handle public properties' - 1 * mockNetworkCmProxyDataService.getCmHandlePublicProperties('some-cm-handle') >> publicProperties + 1 * mockNetworkCmProxyDataService + .getCmHandlePublicProperties('some-cm-handle') >> publicProperties when: 'the cm handle properties api is invoked' - def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the public properties' assertContainsPublicProperties(response) } - def 'Get Cm Handle composite state by Cm Handle id.' () { + def 'Get Cm Handle composite state by Cm Handle id.'() { given: 'a cm handle state endpoint' def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state" and: 'some cm handle composite state' def compositeState = compositeStateTestObject() and: 'the service method is invoked with the cm handle id returning the cm handle composite state' - 1 * mockNetworkCmProxyDataService.getCmHandleCompositeState('some-cm-handle') >> compositeState + 1 * mockNetworkCmProxyDataService + .getCmHandleCompositeState('some-cm-handle') >> compositeState when: 'the cm handle state api is invoked' - def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the cm handle state' @@ -323,13 +356,14 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns two cm handles' def cmHandel1 = new NcmpServiceCmHandle() cmHandel1.cmHandleId = 'some-cmhandle-id1' - cmHandel1.publicProperties = [color:'yellow'] + cmHandel1.publicProperties = [color: 'yellow'] def cmHandel2 = new NcmpServiceCmHandle() cmHandel2.cmHandleId = 'some-cmhandle-id2' - cmHandel2.publicProperties = [color:'green'] + cmHandel2.publicProperties = [color: 'green'] mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2] when: 'the searches api is invoked' - def response = mvc.perform(post(searchesEndpoint) + def response = mvc.perform( + post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) .content(jsonString)).andReturn().response then: 'an empty cm handle identifier is returned' @@ -342,9 +376,10 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns cm handle ids' 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2'] when: 'the searches api is invoked' - def response = mvc.perform(post(searchesEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .content('{}')).andReturn().response + def response = mvc.perform( + post(searchesEndpoint) + .contentType(MediaType.APPLICATION_JSON) + .content('{}')).andReturn().response then: 'cm handle ids are returned' response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]' } @@ -353,37 +388,39 @@ class NetworkCmProxyControllerSpec extends Specification { when: 'the searches api is invoked' def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches" def invalidInputData = '{invalidJson}' - def response = mvc.perform(post(searchesEndpoint) + def response = mvc.perform( + post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) .content(invalidInputData)).andReturn().response then: 'BAD_REQUEST is returned' response.getStatus() == 400 } - def 'Patch resource data in pass-through running datastore.' () { + def 'Patch resource data in pass-through running datastore.'() { given: 'patch resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" when: 'patch data resource request is performed' def response = mvc.perform( - patch(url) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON).content(requestBody) + patch(url) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON).content(requestBody) ).andReturn().response then: 'ncmp service method to update resource is called' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8') + 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8') and: 'the response status is OK' response.status == HttpStatus.OK.value() } - def 'Delete resource data in pass-through running datastore.' () { + def 'Delete resource data in pass-through running datastore.'() { given: 'delete resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" when: 'delete data resource request is performed' def response = mvc.perform( - delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response + delete(url) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response then: 'the ncmp service method to delete resource is called (with null as body)' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8') @@ -394,12 +431,12 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name" + "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON_VALUE) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON_VALUE) ).andReturn().response then: 'async request id is generated' assert response.contentAsString.contains("requestId") @@ -409,32 +446,68 @@ class NetworkCmProxyControllerSpec extends Specification { ':passthrough-running' | 'passthrough-running' } - def 'Get module definitions based on cmHandleId.' () { + def 'Get module definitions based on cmHandleId.'() { when: 'get module definition request is performed' - def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions")) - .andReturn().response + def response = mvc.perform( + get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions")) + .andReturn().response then: 'ncmp service method to get module definitions is called' mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle') - >> [new ModuleDefinition('sampleModuleName', '2021-10-03', - 'module sampleModuleName{ sample module content }')] + >> [new ModuleDefinition('sampleModuleName', '2021-10-03', + 'module sampleModuleName{ sample module content }')] and: 'response contains an array with the module name, revision and content' response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]' and: 'response returns an OK http code' response.status == HttpStatus.OK.value() } - def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario' () { + def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() { when: 'the set data sync enabled request is invoked' - def response = mvc.perform(put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag)) - .andReturn().response + def response = mvc.perform( + put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag)) + .andReturn().response then: 'method to set data sync enabled is called' 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) and: 'the response returns an OK http code' response.status == HttpStatus.OK.value() where: 'the following parameters are used' - scenario | dataSyncEnabledFlag - 'enabled' | true - 'disabled' | false + scenario | dataSyncEnabledFlag + 'enabled' | true + 'disabled' | false + } + + def 'Get Resource Data from operational without descendants.'() { + given: 'resource data url' + def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" + + "?resourceIdentifier=parent/child&include-descendants=false" + when: 'get data resource request is performed' + def response = mvc.perform( + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + ).andReturn().response + then: 'the NCMP data service is called with getResourceDataOperational' + 1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle', + 'parent/child', + FetchDescendantsOption.OMIT_DESCENDANTS) + and: 'response status is Ok' + response.status == HttpStatus.OK.value() + } + + def 'Get Resource Data from operational including descendants.'() { + given: 'resource data url' + def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" + + "?resourceIdentifier=parent/child&include-descendants=true" + when: 'get data resource request is performed' + def response = mvc.perform( + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + ).andReturn().response + then: 'the NCMP data service is called with getResourceDataOperational' + 1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle', + 'parent/child', + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + and: 'response status is Ok' + response.status == HttpStatus.OK.value() } def dataStores() { @@ -453,7 +526,7 @@ class NetworkCmProxyControllerSpec extends Specification { } def assertContainsAll(response, assertContent) { - assertContent.forEach( string -> { assert(response.contentAsString.contains(string)) }) + assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) }) return void } @@ -476,8 +549,8 @@ class NetworkCmProxyControllerSpec extends Specification { def assertContainsPublicProperties(response) { def expectedContent = [ - '"publicCmHandleProperties":' , - '"public prop"' , + '"publicCmHandleProperties":', + '"public prop"', '"some public property"' ] return assertContainsAll(response, expectedContent) diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy new file mode 100644 index 0000000000..3f7a8a5ce2 --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy @@ -0,0 +1,20 @@ +package org.onap.cps.ncmp.rest.controller.handlers + +import spock.lang.Specification + +class NcmpDatastoreResourceRequestHandlerFactorySpec extends Specification { + + def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(null, null) + + def 'Creating ncmp datastore request handlers.'() { + when: 'a ncmp datastore request handler is created for #datastoreType' + def result = objectUnderTest.getNcmpDatastoreResourceRequestHandler(datastoreType) + then: 'the result is of the expected class' + result.class == expectedClass + where: 'the following type of datastore is used' + datastoreType || expectedClass + DatastoreType.OPERATIONAL || NcmpDatastoreOperationalResourceRequestHandler + DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpDatastorePassthroughOperationalResourceRequestHandler + DatastoreType.PASSTHROUGH_RUNNING || NcmpDatastorePassthroughRunningResourceRequestHandler + } +}
\ No newline at end of file diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy index ce908e7547..b8b7fe3bc6 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -29,8 +29,9 @@ import org.onap.cps.ncmp.api.impl.exception.DmiRequestException import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper -import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException @@ -48,9 +49,7 @@ import spock.lang.Specification import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMP import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY -import static org.springframework.http.HttpStatus.BAD_REQUEST -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR -import static org.springframework.http.HttpStatus.NOT_FOUND +import static org.springframework.http.HttpStatus.* import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post @@ -78,6 +77,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean DeprecationHelper stubbedDeprecationHelper = Stub() + @SpringBean + NcmpDatastoreResourceRequestHandlerFactory mockedNcmpDatastoreResourceRequestHandlerFactory = Mock() + @Value('${rest.api.ncmp-base-path}') def basePathNcmp @@ -125,7 +127,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { def 'Failing DMI Request - passthrough scenario'() { given: 'failing DMI request' - setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400) , NCMP) + setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400), NCMP) when: 'the DMI request is executed' def response = performTestRequest(NCMP) then: 'NCMP service responds with 502 Bad Gateway status' @@ -150,7 +152,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { return mvc.perform(post("$dataNodeBaseEndpointNcmpInventory/ch").contentType(MediaType.APPLICATION_JSON).content(jsonData)).andReturn().response } - static void assertTestResponse(response, expectedStatus , expectedErrorMessage , expectedErrorDetails) { + static void assertTestResponse(response, expectedStatus, expectedErrorMessage, expectedErrorDetails) { assert response.status == expectedStatus.value() def content = new JsonSlurper().parseText(response.contentAsString) assert content['status'].toString().contains(expectedStatus.toString()) diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy new file mode 100644 index 0000000000..e626e1505d --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy @@ -0,0 +1,47 @@ +/* + * ============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.ncmp.rest.util + +import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException +import spock.lang.Specification + +class TopicValidatorSpec extends Specification { + + def 'Valid topic name validation.'() { + when: 'a valid topic name is validated' + TopicValidator.validateTopicName('my-valid-topic') + then: 'no exception is thrown' + noExceptionThrown() + } + + def 'Validating invalid topic names.'() { + when: 'the invalid topic name is validated' + TopicValidator.validateTopicName(topicName) + then: 'boolean response will be returned for #scenario' + thrown(InvalidTopicException) + where: 'the following names are used' + scenario | topicName + 'empty topic' | '' + 'blank topic' | ' ' + 'invalid non empty topic' | '1_5_*_#' + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java index 45dba211a3..0ea0674281 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java @@ -33,6 +33,7 @@ import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; @@ -67,6 +68,18 @@ public interface NetworkCmProxyDataService { String requestId); /** + * Get resource data for operational. + * + * @param cmHandleId cm handle identifier + * @param resourceIdentifier resource identifier + * @Link FetchDescendantsOption fetch descendants option + * @return {@code Object} resource data + */ + Object getResourceDataOperational(String cmHandleId, + String resourceIdentifier, + FetchDescendantsOption fetchDescendantsOption); + + /** * Get resource data for data store pass-through running * using dmi. * diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index 5b072f35ed..c21c74bfb6 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.api.impl; +import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateCmHandleQueryParameters; @@ -57,6 +58,7 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr import org.onap.cps.ncmp.api.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; @@ -116,24 +118,32 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService final String topicParamInQuery, final String requestId) { final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId, - resourceIdentifier, - optionsParamInQuery, - DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, - requestId, topicParamInQuery); + resourceIdentifier, + optionsParamInQuery, + DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, + requestId, topicParamInQuery); return responseEntity.getBody(); } @Override + public Object getResourceDataOperational(final String cmHandleId, + final String resourceIdentifier, + final FetchDescendantsOption fetchDescendantsOption) { + return cpsDataService.getDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier, + fetchDescendantsOption); + } + + @Override public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId, final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery, final String requestId) { final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId, - resourceIdentifier, - optionsParamInQuery, - DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, - requestId, topicParamInQuery); + resourceIdentifier, + optionsParamInQuery, + DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, + requestId, topicParamInQuery); return responseEntity.getBody(); } @@ -145,7 +155,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService final String dataType) { CpsValidator.validateNameCharacters(cmHandleId); return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, operation, - requestData, dataType); + requestData, dataType); } @Override @@ -196,29 +206,29 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService * Set the data sync enabled flag, along with the data sync state * based on the data sync enabled boolean for the cm handle id provided. * - * @param cmHandleId cm handle id + * @param cmHandleId cm handle id * @param dataSyncEnabled data sync enabled flag */ @Override public void setDataSyncEnabled(final String cmHandleId, final boolean dataSyncEnabled) { CpsValidator.validateNameCharacters(cmHandleId); final CompositeState compositeState = inventoryPersistence - .getCmHandleState(cmHandleId); + .getCmHandleState(cmHandleId); if (compositeState.getDataSyncEnabled().equals(dataSyncEnabled)) { log.info("Data-Sync Enabled flag is already: {} ", dataSyncEnabled); } else if (compositeState.getCmHandleState() != CmHandleState.READY) { throw new CpsException("State mismatch exception.", "Cm-Handle not in READY state. Cm handle state is: " - + compositeState.getCmHandleState()); + + compositeState.getCmHandleState()); } else { final DataStoreSyncState dataStoreSyncState = compositeState.getDataStores() - .getOperationalDataStore().getDataStoreSyncState(); + .getOperationalDataStore().getDataStoreSyncState(); if (!dataSyncEnabled && dataStoreSyncState == DataStoreSyncState.SYNCHRONIZED) { - cpsDataService.deleteDataNode("NFP-Operational", cmHandleId, - "/netconf-state", OffsetDateTime.now()); + cpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, + "/netconf-state", OffsetDateTime.now()); } CompositeStateUtils.setDataSyncEnabledFlagWithDataSyncState(dataSyncEnabled, compositeState); inventoryPersistence.saveCmHandleState(cmHandleId, - compositeState); + compositeState); } } @@ -262,7 +272,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService public Map<String, String> getCmHandlePublicProperties(final String cmHandleId) { CpsValidator.validateNameCharacters(cmHandleId); final YangModelCmHandle yangModelCmHandle = - inventoryPersistence.getYangModelCmHandle(cmHandleId); + inventoryPersistence.getYangModelCmHandle(cmHandleId); final List<YangModelCmHandle.Property> yangModelPublicProperties = yangModelCmHandle.getPublicProperties(); final Map<String, String> cmHandlePublicProperties = new HashMap<>(); YangDataConverter.asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties); @@ -292,12 +302,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(); try { cmHandleRegistrationResponses = dmiPluginRegistration.getCreatedCmHandles().stream() - .map(cmHandle -> - YangModelCmHandle.toYangModelCmHandle( - dmiPluginRegistration.getDmiPlugin(), - dmiPluginRegistration.getDmiDataPlugin(), - dmiPluginRegistration.getDmiModelPlugin(), - cmHandle)).map(this::registerNewCmHandle).collect(Collectors.toList()); + .map(cmHandle -> + YangModelCmHandle.toYangModelCmHandle( + dmiPluginRegistration.getDmiPlugin(), + dmiPluginRegistration.getDmiDataPlugin(), + dmiPluginRegistration.getDmiModelPlugin(), + cmHandle)).map(this::registerNewCmHandle).collect(Collectors.toList()); } catch (final DataValidationException dataValidationException) { cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createFailureResponse(dmiPluginRegistration .getCreatedCmHandles().stream() 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 index 28b49c9666..f3774d9529 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java +++ b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java @@ -22,7 +22,6 @@ package org.onap.cps.utils; import com.google.common.collect.Lists; import java.util.Collection; -import java.util.regex.Pattern; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -33,30 +32,25 @@ import org.onap.cps.spi.exceptions.DataValidationException; public final class CpsValidator { private static final char[] UNSUPPORTED_NAME_CHARACTERS = "!\" #$%&'()*+,./\\:;<=>?@[]^`{|}~".toCharArray(); - private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|" - + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$"); /** * 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); + 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)); + name + " invalid token encountered at position " + + (name.indexOf(unsupportedCharacter) + 1)); } } } } - /** - * Validate kafka topic name pattern. - * @param topicName name of the topic to be validated - */ - public static boolean validateTopicName(final String topicName) { - return topicName != null && TOPIC_NAME_PATTERN.matcher(topicName).matches(); - } + + } 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 index ce728ef1c1..ea7a5d6d1d 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy @@ -25,7 +25,6 @@ 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') @@ -46,17 +45,4 @@ class CpsValidatorSpec extends Specification { 'position 9' | 'nameWith Space' || 'nameWith Space invalid token encountered at position 9' } - def 'Validating topic names.'() { - when: 'the topic name is validated' - def isValidTopicName = CpsValidator.validateTopicName(topicName) - then: 'boolean response will be returned for #scenario' - assert isValidTopicName == booleanResponse - where: 'the following names are used' - scenario | topicName || booleanResponse - 'valid topic' | 'my-topic-name' || true - 'empty topic' | '' || false - 'blank topic' | ' ' || false - 'null topic' | null || false - 'invalid non empty topic' | '1_5_*_#' || false - } } |