type: string
example: my-module-revision
+ CmHandleQueryRestParameters:
+ type: object
+ title: Cm Handle query parameters for executing cm handle search
+ properties:
+ publicCmHandleProperties:
+ type: object
+ additionalProperties:
+ type: string
+ example: Book Type
+
RestOutputCmHandle:
type: object
title: CM handle Details
application/json:
schema:
$ref: 'components.yaml#/components/schemas/RestOutputCmHandle'
+ 404:
+ $ref: 'components.yaml#/components/responses/NotFound'
+ 500:
+ $ref: 'components.yaml#/components/responses/InternalServerError'
+
+queryCmHandles:
+ post:
+ description: Execute cm handle query search
+ tags:
+ - network-cm-proxy
+ summary: Execute cm handle query upon a given set of query parameters
+ operationId: queryCmHandles
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yaml#/components/schemas/CmHandleQueryRestParameters'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
400:
$ref: 'components.yaml#/components/responses/BadRequest'
401:
$ref: 'ncmp.yml#/executeCmHandleSearch'
/v1/ch/{cm-handle}:
- $ref: 'ncmp.yml#/retrieveCmHandleDetailsById'
\ No newline at end of file
+ $ref: 'ncmp.yml#/retrieveCmHandleDetailsById'
+
+ /v1/data/ch/searches:
+ $ref: 'ncmp.yml#/queryCmHandles'
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
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.models.CmHandleQueryApiParameters;
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
import org.onap.cps.ncmp.rest.model.CmHandleProperties;
import org.onap.cps.ncmp.rest.model.CmHandleProperty;
import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties;
+import org.onap.cps.ncmp.rest.model.CmHandleQueryRestParameters;
import org.onap.cps.ncmp.rest.model.CmHandles;
import org.onap.cps.ncmp.rest.model.ConditionProperties;
import org.onap.cps.ncmp.rest.model.Conditions;
import org.onap.cps.ncmp.rest.model.ModuleNamesAsJsonArray;
import org.onap.cps.ncmp.rest.model.RestModuleReference;
import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
+import org.onap.cps.utils.CpsValidator;
import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;
+ public static final String ASYNC_REQUEST_ID = "requestId";
private final NetworkCmProxyDataService networkCmProxyDataService;
private final JsonObjectMapper jsonObjectMapper;
final @NotNull @Valid String resourceIdentifier,
final @Valid String optionsParamInQuery,
final @Valid String topicParamInQuery) {
+ final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery);
+ final Map<String, Object> asyncResponseData = asyncResponse.getBody();
+
final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle(cmHandle,
resourceIdentifier,
optionsParamInQuery,
- topicParamInQuery);
- return ResponseEntity.ok(responseObject);
+ asyncResponseData == null ? NO_TOPIC : topicParamInQuery,
+ asyncResponseData == null ? NO_REQUEST_ID : asyncResponseData.get(ASYNC_REQUEST_ID).toString());
+
+ if (asyncResponseData == null) {
+ return ResponseEntity.ok(responseObject);
+ }
+ return ResponseEntity.ok(asyncResponse);
}
/**
final @NotNull @Valid String resourceIdentifier,
final @Valid String optionsParamInQuery,
final @Valid String topicParamInQuery) {
+ final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery);
+ final Map<String, Object> asyncResponseData = asyncResponse.getBody();
+
final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(cmHandle,
resourceIdentifier,
optionsParamInQuery,
- topicParamInQuery);
- return ResponseEntity.ok(responseObject);
+ asyncResponseData == null ? NO_TOPIC : topicParamInQuery,
+ asyncResponseData == null ? NO_REQUEST_ID : asyncResponseData.get(ASYNC_REQUEST_ID).toString());
+
+ if (asyncResponseData == null) {
+ return ResponseEntity.ok(responseObject);
+ }
+ return ResponseEntity.ok(asyncResponse);
}
@Override
return ResponseEntity.ok(cmHandles);
}
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryRestParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ public ResponseEntity<List<String>> queryCmHandles(
+ final CmHandleQueryRestParameters cmHandleQueryRestParameters) {
+ final Set<String> cmHandleIds = networkCmProxyDataService.queryCmHandles(
+ jsonObjectMapper.convertToValueType(cmHandleQueryRestParameters, CmHandleQueryApiParameters.class));
+ return ResponseEntity.ok(List.copyOf(cmHandleIds));
+ }
+
/**
* Search for Cm Handle and Properties by Name.
* @param cmHandleId cm-handle identifier
restOutputCmHandle.setPublicCmHandleProperties(cmHandlePublicProperties);
return restOutputCmHandle;
}
+
+ private ResponseEntity<Map<String, Object>> populateAsyncResponse(final String topicParamInQuery) {
+ final boolean processAsynchronously = hasTopicParameter(topicParamInQuery);
+ final Map<String, Object> responseData;
+ if (processAsynchronously) {
+ responseData = getAsyncResponseData();
+ } else {
+ responseData = null;
+ }
+ return ResponseEntity.ok().body(responseData);
+ }
+
+ private static boolean hasTopicParameter(final String topicName) {
+ if (topicName == null) {
+ return false;
+ }
+ if (CpsValidator.validateTopicName(topicName)) {
+ return true;
+ }
+ throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic");
+ }
+
+ private Map<String, Object> getAsyncResponseData() {
+ final Map<String, Object> asyncResponseData = new HashMap<>(1);
+ final String resourceDataRequestId = UUID.randomUUID().toString();
+ asyncResponseData.put(ASYNC_REQUEST_ID, resourceDataRequestId);
+ return asyncResponseData;
+ }
+
}
NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
@SpringBean
- JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+ ObjectMapper objectMapper = new ObjectMapper()
+
+ @SpringBean
+ JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
@SpringBean
NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
@Shared
def NO_TOPIC = null
+ def NO_REQUEST_ID = null
def 'Get Resource Data from pass-through operational.'() {
given: 'resource data url'
1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
'parent/child',
'(a=1,b=2)',
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
and: 'response status is Ok'
response.status == HttpStatus.OK.value()
}
- def 'Get Resource Data from pass-through operational with #scenario.'() {
+ def 'Get Resource Data from #datastoreInUrl with #scenario.'() {
given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
"?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
when: 'get data resource request is performed'
def response = mvc.perform(
.contentType(MediaType.APPLICATION_JSON)
).andReturn().response
then: 'the NCMP data service is called with operational data for cm handle'
- 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
+ expectedNumberOfMethodExecutions
+ * mockNetworkCmProxyDataService."${expectedMethodName}"('testCmHandle',
'parent/child',
'(a=1,b=2)',
- expectedTopicName)
- and: 'response status is Ok'
- response.status == HttpStatus.OK.value()
+ expectedTopicName,
+ _)
+ then: 'response status is expected'
+ response.status == expectedHttpStatus
where: 'the following parameters are used'
- scenario | topicQueryParam || expectedTopicName
- 'Url with valid topic' | "&topic=my-topic-name" || "my-topic-name"
- 'No topic in url' | '' || NO_TOPIC
- 'Null topic in url' | "&topic=null" || "null"
- 'Empty topic in url' | "&topic=\"\"" || "\"\""
- 'Missing topic in url' | "&topic=" || ""
+ scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedMethodName | expectedNumberOfMethodExecutions | expectedHttpStatus
+ 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 'getResourceDataOperationalForCmHandle' | 1 | HttpStatus.OK.value()
+ 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 'getResourceDataOperationalForCmHandle' | 1 | HttpStatus.OK.value()
+ 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 'getResourceDataOperationalForCmHandle' | 1 | HttpStatus.OK.value()
+ 'empty topic in url' | 'passthrough-operational' | '&topic=\"\"' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'missing topic in url' | 'passthrough-operational' | '&topic=' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'blank topic value in url' | 'passthrough-operational' | '&topic=\" \"' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 'getResourceDataPassThroughRunningForCmHandle' | 1 | HttpStatus.OK.value()
+ 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 'getResourceDataPassThroughRunningForCmHandle' | 1 | HttpStatus.OK.value()
+ 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 'getResourceDataPassThroughRunningForCmHandle' | 1 | HttpStatus.OK.value()
+ 'empty topic in url' | 'passthrough-running' | '&topic=\"\"' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'missing topic in url' | 'passthrough-running' | '&topic=' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'blank topic value in url' | 'passthrough-running' | '&topic=\" \"' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'invalid non-empty topic value in url' | 'passthrough-running' | '&topic=1_5_*_#' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
}
def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
resourceIdentifier,
'(a=1,b=2)',
- NO_TOPIC) >> '{valid-json}'
+ NO_TOPIC,
+ NO_REQUEST_ID) >> '{valid-json}'
when: 'get data resource request is performed'
def response = mvc.perform(
get(getUrl)
response.contentAsString == '{"cmHandles":[]}'
}
+ def 'Query for cm handles matching query parameters'() {
+ given: 'an endpoint and json data'
+ def searchesEndpoint = "$ncmpBasePathV1/data/ch/searches"
+ String jsonString = '{"publicCmHandleProperties": {"name": "Contact", "value": "newemailforstore@bookstore.com"}}'
+ and: 'the service method is invoked with module names and returns cm handle ids'
+ 1 * mockNetworkCmProxyDataService.queryCmHandles(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2']
+ when: 'the searches api is invoked'
+ def response = mvc.perform(post(searchesEndpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonString)).andReturn().response
+ then: 'cm handle ids are returned'
+ response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]'
+ }
+
+ def 'Query for cm handles with invalid request payload'() {
+ when: 'the searches api is invoked'
+ def searchesEndpoint = "$ncmpBasePathV1/data/ch/searches"
+ def invalidInputData = '{invalidJson}'
+ 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.' () {
given: 'patch resource data url'
def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
and: 'the response is No Content'
response.status == HttpStatus.NO_CONTENT.value()
}
+
+ 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"
+ when: 'get data resource request is performed'
+ def response = mvc.perform(
+ get(getUrl)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ ).andReturn().response
+ then: 'async request id is generated'
+ assert response.contentAsString.contains("requestId")
+ where: 'the following parameters are used'
+ scenario | datastoreInUrl
+ ':passthrough-operational' | 'passthrough-operational'
+ ':passthrough-running' | 'passthrough-running'
+ }
+
}
import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
import java.util.Collection;
+import java.util.Set;
+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;
* @param resourceIdentifier resource identifier
* @param optionsParamInQuery options query
* @param topicParamInQuery topic name for (triggering) async responses
+ * @param requestId unique requestId for async request
* @return {@code Object} resource data
*/
Object getResourceDataOperationalForCmHandle(String cmHandleId,
String resourceIdentifier,
String optionsParamInQuery,
- String topicParamInQuery);
+ String topicParamInQuery,
+ String requestId);
/**
* Get resource data for data store pass-through running
* @param cmHandleId cm handle identifier
* @param resourceIdentifier resource identifier
* @param optionsParamInQuery options query
- * @param topicParamInQuery topic query
+ * @param topicParamInQuery topic name for (triggering) async responses
+ * @param requestId unique requestId for async request
* @return {@code Object} resource data
*/
Object getResourceDataPassThroughRunningForCmHandle(String cmHandleId,
String resourceIdentifier,
String optionsParamInQuery,
- String topicParamInQuery);
+ String topicParamInQuery,
+ String requestId);
/**
* Write resource data for data store pass-through running
*/
NcmpServiceCmHandle getNcmpServiceCmHandle(String cmHandleId);
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryApiParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryApiParameters cmHandleQueryApiParameters);
}
import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
+import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
-import java.util.regex.Pattern;
+import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.CpsModuleService;
import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
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.operations.DmiDataOperations;
import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever;
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError;
import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
import org.onap.cps.spi.model.ModuleReference;
import org.onap.cps.utils.CpsValidator;
import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
private final YangModelCmHandleRetriever yangModelCmHandleRetriever;
- // valid kafka topic name regex
- private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|"
- + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$");
- private static final String NO_REQUEST_ID = null;
- private static final String NO_TOPIC = null;
-
@Override
public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(
final DmiPluginRegistration dmiPluginRegistration) {
public Object getResourceDataOperationalForCmHandle(final String cmHandleId,
final String resourceIdentifier,
final String optionsParamInQuery,
- final String topicParamInQuery) {
+ final String topicParamInQuery,
+ final String requestId) {
CpsValidator.validateNameCharacters(cmHandleId);
- return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier,
- DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery);
+ return getResourceDataResponse(cmHandleId, resourceIdentifier,
+ DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery, requestId);
}
@Override
public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId,
final String resourceIdentifier,
final String optionsParamInQuery,
- final String topicParamInQuery) {
+ final String topicParamInQuery,
+ final String requestId) {
CpsValidator.validateNameCharacters(cmHandleId);
- return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier,
- DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery);
+ return getResourceDataResponse(cmHandleId, resourceIdentifier,
+ DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery, requestId);
}
@Override
return cpsAdminService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNames);
}
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
+
+ cmHandleQueryApiParameters.getPublicProperties().forEach((key, value) -> {
+ if (Strings.isNullOrEmpty(key)) {
+ throw new DataValidationException("Invalid Query Parameter.",
+ "Missing property name - please supply a valid name.");
+ }
+ });
+
+ return cpsAdminService.queryCmHandles(jsonObjectMapper.convertToValueType(cmHandleQueryApiParameters,
+ org.onap.cps.spi.model.CmHandleQueryParameters.class));
+ }
+
/**
* Retrieve cm handle details for a given cm handle.
*
yangModelCmHandle.getId());
}
- private static boolean hasTopicParameter(final String topicName) {
- if (topicName == null) {
- return false;
- }
- if (TOPIC_NAME_PATTERN.matcher(topicName).matches()) {
- return true;
- }
- throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic");
- }
-
- private Map<String, Object> buildDmiResponse(final String requestId) {
- final Map<String, Object> dmiResponseMap = new HashMap<>();
- dmiResponseMap.put("requestId", requestId);
- return dmiResponseMap;
- }
-
- private Object validateTopicNameAndGetResourceData(final String cmHandleId,
- final String resourceIdentifier,
- final DmiOperations.DataStoreEnum dataStore,
- final String optionsParamInQuery,
- final String topicParamInQuery) {
- final boolean processAsynchronously = hasTopicParameter(topicParamInQuery);
- if (processAsynchronously) {
- final String resourceDataRequestId = UUID.randomUUID().toString();
- return ResponseEntity.status(HttpStatus.OK)
- .body(buildDmiResponse(resourceDataRequestId));
- }
+ private Object getResourceDataResponse(final String cmHandleId,
+ final String resourceIdentifier,
+ final DmiOperations.DataStoreEnum dataStore,
+ final String optionsParamInQuery,
+ final String topicParamInQuery,
+ final String requestId) {
final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(
- cmHandleId, resourceIdentifier, optionsParamInQuery, dataStore, NO_REQUEST_ID, NO_TOPIC);
+ cmHandleId, resourceIdentifier, optionsParamInQuery, dataStore, requestId, topicParamInQuery);
return handleResponse(responseEntity, OperationEnum.READ);
}
}
\ No newline at end of file
--- /dev/null
+/*
+ * ============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.api.models;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.Map;
+import javax.validation.Valid;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+@JsonInclude(Include.NON_NULL)
+public class CmHandleQueryApiParameters {
+
+ @JsonProperty("publicCmHandleProperties")
+ @Valid
+ private Map<String, String> publicProperties = Collections.emptyMap();
+
+}
package org.onap.cps.ncmp.api.impl
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.operations.YangModelCmHandleRetriever
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
import spock.lang.Shared
def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'DMI returns a json response'
response == 'dmi-response'
}
objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'exception is thrown with the expected response code and details'
def exceptionThrown = thrown(HttpClientRequestException.class)
exceptionThrown.details.contains('NOK-json')
objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'exception is thrown'
def exceptionThrown = thrown(HttpClientRequestException.class)
and: 'details contain the original response'
def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'get resource data returns expected response'
response == '{dmi-response}'
}
objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'exception is thrown'
def exceptionThrown = thrown(HttpClientRequestException.class)
and: 'details contain the original response'
exceptionThrown.httpStatus == HttpStatus.NOT_FOUND.value()
}
- def 'DMI Operational data request with #scenario'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, NO_REQUEST_ID, NO_TOPIC)
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called data operational with blank topic'
- def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '',
- '', emptyTopic)
- then: 'a invalid topic exception is thrown'
- thrown(InvalidTopicException)
- where: 'the following parameters are used'
- scenario | emptyTopic
- 'no topic value in url' | ''
- 'empty topic value in url' | '\"\"'
- 'blank topic value in url' | ' '
- 'invalid non-empty topic value in url' | '1_5_*_#'
- }
-
- def 'Get resource data for data operational from DMI with valid topic i.e. async request.'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode(*_) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, 'my-topic-name')
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called for data operational with valid topic'
- def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '', '', 'my-topic-name')
- then: 'non empty request id is generated'
- assert responseData.body.requestId.length() > 0
- }
-
- def 'Get resource data for pass through running from DMI with valid topic async request.'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, 'my-topic-name')
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called for data operational with valid topic'
- def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
- '', OPTIONS_PARAM, 'my-topic-name')
- then: 'non empty request id is generated'
- assert responseData.body.requestId.length() > 0
- }
-
- def 'DMI pass through running data request with #scenario'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, NO_REQUEST_ID, NO_TOPIC)
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called for data operational with valid topic'
- def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
- '', '', emptyTopic)
- then: 'a invalid topic exception is thrown'
- thrown(InvalidTopicException)
- where: 'the following parameters are used'
- scenario | emptyTopic
- 'no topic value in url' | ''
- 'empty topic value in url' | '\"\"'
- 'blank topic value in url' | ' '
- 'invalid non-empty topic value in url' | '1_5_*_#'
- }
-
def 'Getting Yang Resources.'() {
when: 'yang resources is called'
objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import lombok.AllArgsConstructor;
import org.onap.cps.spi.exceptions.DataspaceInUseException;
import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
import org.onap.cps.spi.repository.AnchorRepository;
import org.onap.cps.spi.repository.DataspaceRepository;
+import org.onap.cps.spi.repository.ModuleReferenceRepository;
import org.onap.cps.spi.repository.SchemaSetRepository;
import org.onap.cps.spi.repository.YangResourceRepository;
import org.springframework.dao.DataIntegrityViolationException;
private final AnchorRepository anchorRepository;
private final SchemaSetRepository schemaSetRepository;
private final YangResourceRepository yangResourceRepository;
+ private final ModuleReferenceRepository moduleReferenceRepository;
@Override
public void createDataspace(final String dataspaceName) {
anchorRepository.delete(anchorEntity);
}
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) {
+ return moduleReferenceRepository.queryCmHandles(cmHandleQueryParameters);
+ }
+
private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) {
final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
package org.onap.cps.spi.repository;
import java.util.Collection;
+import java.util.Set;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
import org.onap.cps.spi.model.ModuleReference;
/**
Collection<ModuleReference> identifyNewModuleReferences(
final Collection<ModuleReference> moduleReferencesToCheck);
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters);
+
}
import org.springframework.stereotype.Repository;
@Repository
-public interface ModuleReferenceRepository extends
- JpaRepository<YangResourceEntity, Long>, ModuleReferenceQuery {
+public interface ModuleReferenceRepository extends JpaRepository<YangResourceEntity, Long>, ModuleReferenceQuery {
Collection<ModuleReference> identifyNewModuleReferences(
final Collection<ModuleReference> moduleReferencesToCheck);
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
+import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
+import org.onap.cps.spi.model.DataNode;
import org.onap.cps.spi.model.ModuleReference;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Transactional
+@AllArgsConstructor
public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
@PersistenceContext
private EntityManager entityManager;
+ private final CpsDataPersistenceService cpsDataPersistenceService;
+
@Override
@SneakyThrows
public Collection<ModuleReference> identifyNewModuleReferences(
return identifyNewModuleReferencesForCmHandle(tempTableName);
}
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) {
+
+ if (cmHandleQueryParameters.getPublicProperties().entrySet().isEmpty()) {
+ return getAllCmHandles();
+ }
+
+ final Collection<DataNode> amalgamatedQueryResult = new ArrayList<>();
+ int queryConditionCounter = 0;
+ for (final Map.Entry<String, String> entry : cmHandleQueryParameters.getPublicProperties().entrySet()) {
+ final StringBuilder cmHandlePath = new StringBuilder();
+ cmHandlePath.append("//public-properties[@name='").append(entry.getKey()).append("' ");
+ cmHandlePath.append("and @value='").append(entry.getValue()).append("']");
+ cmHandlePath.append("/ancestor::cm-handles");
+
+ final Collection<DataNode> singleConditionQueryResult =
+ cpsDataPersistenceService.queryDataNodes("NCMP-Admin",
+ "ncmp-dmi-registry", String.valueOf(cmHandlePath), FetchDescendantsOption.OMIT_DESCENDANTS);
+ if (++queryConditionCounter == 1) {
+ amalgamatedQueryResult.addAll(singleConditionQueryResult);
+ } else {
+ amalgamatedQueryResult.retainAll(singleConditionQueryResult);
+ }
+
+ if (amalgamatedQueryResult.isEmpty()) {
+ break;
+ }
+ }
+
+ return extractCmHandleIds(amalgamatedQueryResult);
+ }
+
+ private Set<String> getAllCmHandles() {
+ final Collection<DataNode> cmHandles = cpsDataPersistenceService.queryDataNodes("NCMP-Admin",
+ "ncmp-dmi-registry", "//public-properties/ancestor::cm-handles",
+ FetchDescendantsOption.OMIT_DESCENDANTS);
+ return extractCmHandleIds(cmHandles);
+ }
+
+ private Set<String> extractCmHandleIds(final Collection<DataNode> cmHandles) {
+ return cmHandles.stream().map(cmHandle -> cmHandle.getLeaves().get("id").toString())
+ .collect(Collectors.toSet());
+ }
+
private void createTemporaryTable(final String tempTableName) {
final StringBuilder sqlStringBuilder = new StringBuilder("CREATE TEMPORARY TABLE " + tempTableName + "(");
sqlStringBuilder.append(" id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,");
+ " WHERE yang_resource.module_name IS NULL;", tempTableName);
final List<Object[]> resultsAsObjects =
- entityManager.createNativeQuery(sql).getResultList();
+ (List<Object[]>) entityManager.createNativeQuery(sql).getResultList();
final List<ModuleReference> resultsAsModuleReferences = new ArrayList<>(resultsAsObjects.size());
for (final Object[] row : resultsAsObjects) {
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021 Nordix Foundation
+ * Copyright (C) 2021-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 Bell Canada
* ================================================================================
package org.onap.cps.spi.impl
+import org.mockito.Mock
import org.onap.cps.spi.CpsAdminPersistenceService
import org.onap.cps.spi.exceptions.AlreadyDefinedException
import org.onap.cps.spi.exceptions.AnchorNotFoundException
import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException
import org.onap.cps.spi.model.Anchor
+import org.onap.cps.spi.model.CmHandleQueryParameters
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper
class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
@Autowired
CpsAdminPersistenceService objectUnderTest
+ @Mock
+ ObjectMapper objectMapper
+
static final String SET_DATA = '/data/anchor.sql'
+ static final String SET_FRAGMENT_DATA = '/data/fragment.sql'
static final String SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES = '/data/anchors-schemaset-modules.sql'
static final String DATASPACE_WITH_NO_DATA = 'DATASPACE-002-NO-DATA'
static final Integer DELETED_ANCHOR_ID = 3002
'dataspace contains schemasets' | 'DATASPACE-003' || DataspaceInUseException | 'contains 1 schemaset(s)'
}
+ @Sql([CLEAR_DATA, SET_FRAGMENT_DATA])
+ def 'Retrieve cm handle ids when #scenario.'() {
+ when: 'the service is invoked'
+ def cmHandleQueryParameters = new CmHandleQueryParameters()
+ cmHandleQueryParameters.setPublicProperties(publicProperties)
+ def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
+ then: 'the correct expected cm handles are returned'
+ returnedCmHandles == expectedCmHandleIds
+ where: 'the following data is used'
+ scenario | publicProperties || expectedCmHandleIds
+ 'single matching property' | ['Contact' : 'newemailforstore@bookstore.com'] || ['PNFDemo2', 'PNFDemo', 'PNFDemo4'] as Set
+ 'public property dont match' | ['wont_match' : 'wont_match'] || [] as Set
+ '2 properties, only one match (and)' | ['Contact' : 'newemailforstore@bookstore.com', 'Contact2': 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] as Set
+ '2 properties, no match (and)' | ['Contact' : 'newemailforstore@bookstore.com', 'Contact2': ''] || [] as Set
+ 'No public properties - return all cm handles' | [ : ] || ['PNFDemo3', 'PNFDemo', 'PNFDemo2', 'PNFDemo4'] as Set
+ }
}
/*
============LICENSE_START=======================================================
- Copyright (C) 2021 Nordix Foundation.
+ Copyright (C) 2021-2022 Nordix Foundation.
Modifications Copyright (C) 2021 Pantheon.tech
Modifications Copyright (C) 2021-2022 Bell Canada.
================================================================================
*/
INSERT INTO DATASPACE (ID, NAME) VALUES
- (1001, 'DATASPACE-001');
+ (1001, 'DATASPACE-001'),
+ (1002, 'NCMP-Admin');
INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES
(2001, 'SCHEMA-SET-001', 1001);
INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
(3001, 'ANCHOR-001', 1001, 2001),
- (3003, 'ANCHOR-003', 1001, 2001);
+ (3003, 'ANCHOR-003', 1001, 2001),
+ (3004, 'ncmp-dmi-registry', 1002, 2001);
INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES
(4001, 1001, 3001, null, '/parent-1'),
(4230, 1001, 3003, 4227, '/parent-206/child-206/grand-child-206[@key="X"]', '{"key": "X"}'),
(4231, 1001, 3003, null, '/parent-206[@key="A"]', '{"key": "A"}'),
(4232, 1001, 3003, 4231, '/parent-206[@key="A"]/child-206', '{}'),
- (4233, 1001, 3003, null, '/parent-206[@key="B"]', '{"key": "B"}');
\ No newline at end of file
+ (4233, 1001, 3003, null, '/parent-206[@key="B"]', '{"key": "B"}');
+
+INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
+ (5000, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo"]', '{"id": "PNFDemo", "dmi-service-name": "http://172.21.235.14:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5001, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo2"]', '{"id": "PNFDemo2", "dmi-service-name": "http://172.26.46.68:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5002, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo3"]', '{"id": "PNFDemo3", "dmi-service-name": "http://172.26.46.68:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5003, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo4"]', '{"id": "PNFDemo4", "dmi-service-name": "http://172.26.46.68:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5004, 1002, 3004, 5000, '/dmi-registry/cm-handles[@id="PNFDemo"]/public-properties[@name="Contact"]', '{"name": "Contact", "value": "newemailforstore@bookstore.com"}'),
+ (5005, 1002, 3004, 5001, '/dmi-registry/cm-handles[@id="PNFDemo2"]/public-properties[@name="Contact"]', '{"name": "Contact", "value": "newemailforstore@bookstore.com"}'),
+ (5006, 1002, 3004, 5002, '/dmi-registry/cm-handles[@id="PNFDemo3"]/public-properties[@name="Contact"]', '{"name": "Contact3", "value": "PNF3@bookstore.com"}'),
+ (5007, 1002, 3004, 5003, '/dmi-registry/cm-handles[@id="PNFDemo4"]/public-properties[@name="Contact"]', '{"name": "Contact", "value": "newemailforstore@bookstore.com"}'),
+ (5008, 1002, 3004, 5004, '/dmi-registry/cm-handles[@id="PNFDemo4"]/public-properties[@name="Contact2"]', '{"name": "Contact2", "value": "newemailforstore2@bookstore.com"}');
\ No newline at end of file
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2021 Nordix Foundation
+ * Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
package org.onap.cps.api;
import java.util.Collection;
+import java.util.Set;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.exceptions.CpsException;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
/**
* CPS Admin Service.
* given module names
*/
Collection<String> queryAnchorNames(String dataspaceName, Collection<String> moduleNames);
+
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters);
}
import java.time.OffsetDateTime;
import java.util.Collection;
+import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.onap.cps.api.CpsAdminService;
import org.onap.cps.api.CpsDataService;
import org.onap.cps.spi.CpsAdminPersistenceService;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
import org.onap.cps.utils.CpsValidator;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames);
return anchors.stream().map(Anchor::getName).collect(Collectors.toList());
}
+
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) {
+ return cpsAdminPersistenceService.queryCmHandles(cmHandleQueryParameters);
+ }
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation.
+ * Copyright (C) 2020-2022 Nordix Foundation.
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
package org.onap.cps.spi;
import java.util.Collection;
+import java.util.Set;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
/*
Service for handling CPS admin data.
* @param anchorName anchor name
*/
void deleteAnchor(String dataspaceName, String anchorName);
+
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters);
}
--- /dev/null
+/*
+ * ============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.spi.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.Map;
+import javax.validation.Valid;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+@JsonInclude(Include.NON_NULL)
+public class CmHandleQueryParameters {
+
+ @JsonProperty("publicCmHandleProperties")
+ @Valid
+ private Map<String, String> publicProperties = Collections.emptyMap();
+
+}
import java.util.Collections;
import java.util.Map;
import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Setter(AccessLevel.PROTECTED)
@Getter
+@EqualsAndHashCode
public class DataNode {
DataNode() { }
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;
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.
}
}
}
+
+ /**
+ * 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();
+ }
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation
+ * Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
import org.onap.cps.api.CpsDataService
import org.onap.cps.spi.CpsAdminPersistenceService
import org.onap.cps.spi.model.Anchor
+import org.onap.cps.spi.model.CmHandleQueryParameters
import spock.lang.Specification
import java.time.OffsetDateTime
1 * mockCpsAdminPersistenceService.deleteDataspace('someDataspace')
}
+ def 'Query CM Handles.'() {
+ given: 'a cm handle query'
+ def cmHandleQueryParameters = new CmHandleQueryParameters()
+ when: 'query cm handles is invoked'
+ objectUnderTest.queryCmHandles(cmHandleQueryParameters)
+ then: 'associated persistence service method is invoked with correct parameter'
+ 1 * mockCpsAdminPersistenceService.queryCmHandles(cmHandleQueryParameters)
+ }
+
}
'position 5' | 'name with spaces' || 'name with spaces invalid token encountered at position 5'
'position 9' | 'nameWith Space' || 'nameWith Space invalid token encountered at position 9'
}
+
+ 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
+ }
}
+++ /dev/null
-{
- "cmHandles": [
- "PNFDemo"
- ]
-}
\ No newline at end of file
# ============LICENSE_START=======================================================
-# Copyright (C) 2021 Nordix Foundation
+# Copyright (C) 2021-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.
# Test suites are relative paths under csit/tests/.
# Place the suites in run order.
actuator
-cps-model-sync
-ncmp-passthrough
cps-admin
cps-data
-
+cps-model-sync
+ncmp-passthrough
+public-properties-query
\ No newline at end of file
${ncmpInventoryBasePath} /ncmpInventory
${ncmpBasePath} /ncmp
${dmiUrl} http://${DMI_HOST}:${DMI_PORT}
-${jsonDataCreate} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","createdCmHandles":[{"cmHandle":"PNFDemo","cmHandleProperties":{"Book1":"Sci-Fi Book"},"publicCmHandleProperties":{"Contact":"storeemail@bookstore.com"}}]}
+${jsonDataCreate} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","createdCmHandles":[{"cmHandle":"PNFDemo","cmHandleProperties":{"Book1":"Sci-Fi Book"},"publicCmHandleProperties":{"Contact":"storeemail@bookstore.com", "Contact2":"storeemail2@bookstore.com"}}]}
${jsonDataUpdate} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","updatedCmHandles":[{"cmHandle":"PNFDemo","cmHandleProperties":{"Book1":"Romance Book"},"publicCmHandleProperties":{"Contact":"newemailforstore@bookstore.com"}}]}
*** Test Cases ***
--- /dev/null
+/*
+ * ============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=========================================================
+ */
+
+*** Settings ***
+Documentation Public Properties Query Test
+
+Library Collections
+Library OperatingSystem
+Library RequestsLibrary
+Library BuiltIn
+
+Suite Setup Create Session CPS_URL http://${CPS_CORE_HOST}:${CPS_CORE_PORT}
+
+*** Variables ***
+
+${auth} Basic Y3BzdXNlcjpjcHNyMGNrcyE=
+${ncmpBasePath} /ncmp/v1
+${jsonMatchingQueryParameters} {"publicCmHandleProperties": {"Contact" : "newemailforstore@bookstore.com", "Contact2" : "storeemail2@bookstore.com"}}
+${jsonMissingPropertyQueryParameters} {"publicCmHandleProperties": { "" : "doesnt matter"}}
+
+*** Test Cases ***
+Retrieve CM Handles where query parameters Match
+ ${uri}= Set Variable ${ncmpBasePath}/data/ch/searches
+ ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth}
+ ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonMatchingQueryParameters}
+ ${responseJson}= Set Variable ${response.json()}
+ Should Be Equal As Strings ${response.status_code} 200
+ Should Contain ${responseJson} PNFDemo
+
+Throw 400 when Structure of Request is Incorrect
+ ${uri}= Set Variable ${ncmpBasePath}/data/ch/searches
+ ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth}
+ ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonMissingPropertyQueryParameters} expected_status=400
+ Should Be Equal As Strings ${response} <Response [400]>