import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP;
+import java.io.Serializable;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.CpsDataService;
+import org.onap.cps.ncmp.api.impl.utils.DataNodeHelper;
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
private static final String SUBSCRIPTION_DATASPACE_NAME = "NCMP-Admin";
private static final String SUBSCRIPTION_ANCHOR_NAME = "AVC-Subscriptions";
private static final String SUBSCRIPTION_REGISTRY_PARENT = "/subscription-registry";
-
private final JsonObjectMapper jsonObjectMapper;
private final CpsDataService cpsDataService;
@Override
public void saveSubscriptionEvent(final YangModelSubscriptionEvent yangModelSubscriptionEvent) {
- final String subscriptionEventJsonData =
- createSubscriptionEventJsonData(jsonObjectMapper.asJsonString(yangModelSubscriptionEvent));
+ final String clientId = yangModelSubscriptionEvent.getClientId();
+ final String subscriptionName = yangModelSubscriptionEvent.getSubscriptionName();
+
final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(SUBSCRIPTION_DATASPACE_NAME,
SUBSCRIPTION_ANCHOR_NAME, SUBSCRIPTION_REGISTRY_PARENT, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+
+ if (isSubscriptionRegistryEmptyOrNonExist(dataNodes, clientId, subscriptionName)) {
+ saveSubscriptionEventYangModel(createSubscriptionEventJsonData(
+ jsonObjectMapper.asJsonString(yangModelSubscriptionEvent)));
+ } else {
+ findDeltaCmHandlesAddOrUpdateInDatabase(yangModelSubscriptionEvent, clientId, subscriptionName, dataNodes);
+ }
+ }
+
+ private void findDeltaCmHandlesAddOrUpdateInDatabase(final YangModelSubscriptionEvent yangModelSubscriptionEvent,
+ final String clientId, final String subscriptionName,
+ final Collection<DataNode> dataNodes) {
+ final Map<String, SubscriptionStatus> cmHandleIdsFromYangModel =
+ extractCmHandleFromYangModelAsMap(yangModelSubscriptionEvent);
+ final Map<String, SubscriptionStatus> cmHandleIdsFromDatabase =
+ extractCmHandleFromDbAsMap(dataNodes);
+
+ final Map<String, SubscriptionStatus> newCmHandles =
+ mapDifference(cmHandleIdsFromYangModel, cmHandleIdsFromDatabase);
+ traverseCmHandleList(newCmHandles, clientId, subscriptionName, true);
+
+ final Map<String, SubscriptionStatus> existingCmHandles =
+ mapDifference(cmHandleIdsFromYangModel, newCmHandles);
+ traverseCmHandleList(existingCmHandles, clientId, subscriptionName, false);
+ }
+
+ private boolean isSubscriptionRegistryEmptyOrNonExist(final Collection<DataNode> dataNodes,
+ final String clientId, final String subscriptionName) {
final Optional<DataNode> dataNodeFirst = dataNodes.stream().findFirst();
- final boolean isCreateOperation =
- dataNodeFirst.isPresent() && dataNodeFirst.get().getChildDataNodes().isEmpty();
- saveOrUpdateSubscriptionEventYangModel(subscriptionEventJsonData, isCreateOperation);
+ return ((dataNodeFirst.isPresent() && dataNodeFirst.get().getChildDataNodes().isEmpty())
+ || getCmHandlesForSubscriptionEvent(clientId, subscriptionName).isEmpty());
+ }
+
+ private void traverseCmHandleList(final Map<String, SubscriptionStatus> cmHandleMap,
+ final String clientId,
+ final String subscriptionName,
+ final boolean isAddListElementOperation) {
+ final List<YangModelSubscriptionEvent.TargetCmHandle> cmHandleList =
+ targetCmHandlesAsList(cmHandleMap);
+ for (final YangModelSubscriptionEvent.TargetCmHandle targetCmHandle : cmHandleList) {
+ final String targetCmHandleAsJson =
+ createTargetCmHandleJsonData(jsonObjectMapper.asJsonString(targetCmHandle));
+ addOrReplaceCmHandlePredicateListElement(targetCmHandleAsJson, clientId, subscriptionName,
+ isAddListElementOperation);
+ }
}
- private void saveOrUpdateSubscriptionEventYangModel(final String subscriptionEventJsonData,
- final boolean isCreateOperation) {
- if (isCreateOperation) {
- log.info("SubscriptionEventJsonData to be saved into DB {}", subscriptionEventJsonData);
- cpsDataService.saveListElements(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
- SUBSCRIPTION_REGISTRY_PARENT, subscriptionEventJsonData, NO_TIMESTAMP);
+ private void addOrReplaceCmHandlePredicateListElement(final String targetCmHandleAsJson,
+ final String clientId,
+ final String subscriptionName,
+ final boolean isAddListElementOperation) {
+ if (isAddListElementOperation) {
+ log.info("targetCmHandleAsJson to be added into DB {}", targetCmHandleAsJson);
+ cpsDataService.saveListElements(SUBSCRIPTION_DATASPACE_NAME,
+ SUBSCRIPTION_ANCHOR_NAME, createCmHandleXpathPredicates(clientId, subscriptionName),
+ targetCmHandleAsJson, NO_TIMESTAMP);
} else {
- log.info("SubscriptionEventJsonData to be updated into DB {}", subscriptionEventJsonData);
- cpsDataService.updateDataNodeAndDescendants(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
- SUBSCRIPTION_REGISTRY_PARENT, subscriptionEventJsonData, NO_TIMESTAMP);
+ log.info("targetCmHandleAsJson to be updated into DB {}", targetCmHandleAsJson);
+ cpsDataService.updateNodeLeaves(SUBSCRIPTION_DATASPACE_NAME,
+ SUBSCRIPTION_ANCHOR_NAME, createCmHandleXpathPredicates(clientId, subscriptionName),
+ targetCmHandleAsJson, NO_TIMESTAMP);
}
}
+ private void saveSubscriptionEventYangModel(final String subscriptionEventJsonData) {
+ log.info("SubscriptionEventJsonData to be saved into DB {}", subscriptionEventJsonData);
+ cpsDataService.saveListElements(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+ SUBSCRIPTION_REGISTRY_PARENT, subscriptionEventJsonData, NO_TIMESTAMP);
+ }
+
@Override
public Collection<DataNode> getDataNodesForSubscriptionEvent() {
return cpsDataService.getDataNodes(SUBSCRIPTION_DATASPACE_NAME,
FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
}
+ @Override
+ public Collection<DataNode> getCmHandlesForSubscriptionEvent(final String clientId, final String subscriptionName) {
+ return cpsDataService.getDataNodesForMultipleXpaths(SUBSCRIPTION_DATASPACE_NAME,
+ SUBSCRIPTION_ANCHOR_NAME, Arrays.asList(createCmHandleXpath(clientId, subscriptionName)),
+ FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+ }
+
+ private static Map<String, SubscriptionStatus> extractCmHandleFromDbAsMap(final Collection<DataNode> dataNodes) {
+ final List<Map<String, Serializable>> dataNodeLeaves = DataNodeHelper.getDataNodeLeaves(dataNodes);
+ final List<Collection<Serializable>> cmHandleIdToStatus = DataNodeHelper.getCmHandleIdToStatus(dataNodeLeaves);
+ return DataNodeHelper.getCmHandleIdToStatusMap(cmHandleIdToStatus);
+ }
+
+ private static Map<String, SubscriptionStatus> extractCmHandleFromYangModelAsMap(
+ final YangModelSubscriptionEvent yangModelSubscriptionEvent) {
+ return yangModelSubscriptionEvent.getPredicates().getTargetCmHandles()
+ .stream().collect(Collectors.toMap(
+ YangModelSubscriptionEvent.TargetCmHandle::getCmHandleId,
+ YangModelSubscriptionEvent.TargetCmHandle::getStatus));
+ }
+
+ private static List<YangModelSubscriptionEvent.TargetCmHandle> targetCmHandlesAsList(
+ final Map<String, SubscriptionStatus> newCmHandles) {
+ return newCmHandles.entrySet().stream().map(entry ->
+ new YangModelSubscriptionEvent.TargetCmHandle(entry.getKey(),
+ entry.getValue())).collect(Collectors.toList());
+ }
+
private static String createSubscriptionEventJsonData(final String yangModelSubscriptionAsJson) {
return "{\"subscription\":[" + yangModelSubscriptionAsJson + "]}";
}
+
+ private static String createTargetCmHandleJsonData(final String targetCmHandleAsJson) {
+ return "{\"targetCmHandles\":[" + targetCmHandleAsJson + "]}";
+ }
+
+ private static String createCmHandleXpathPredicates(final String clientId, final String subscriptionName) {
+ return "/subscription-registry/subscription[@clientID='" + clientId
+ + "' and @subscriptionName='" + subscriptionName + "']/predicates";
+ }
+
+ private static String createCmHandleXpath(final String clientId, final String subscriptionName) {
+ return "/subscription-registry/subscription[@clientID='" + clientId
+ + "' and @subscriptionName='" + subscriptionName + "']";
+ }
+
+ private static <K, V> Map<K, V> mapDifference(final Map<? extends K, ? extends V> left,
+ final Map<? extends K, ? extends V> right) {
+ final Map<K, V> difference = new HashMap<>();
+ difference.putAll(left);
+ difference.putAll(right);
+ difference.entrySet().removeAll(right.entrySet());
+ return difference;
+ }
}
private static final String SUBSCRIPTION_DATASPACE_NAME = "NCMP-Admin";
private static final String SUBSCRIPTION_ANCHOR_NAME = "AVC-Subscriptions";
private static final String SUBSCRIPTION_REGISTRY_PARENT = "/subscription-registry";
+ private static final String SUBSCRIPTION_REGISTRY_PREDICATES_XPATH = "/subscription-registry/subscription[@clientID='some-client-id' and @subscriptionName='some-subscription-name']/predicates";
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockCpsDataService = Mock(CpsDataService)
def yangModelSubscriptionEvent = new YangModelSubscriptionEvent(clientId: 'some-client-id',
subscriptionName: 'some-subscription-name', tagged: true, topic: 'some-topic', predicates: predicates)
- def 'save a subscription event' () {
- given: 'a data node that does not exist in db'
+ def 'save a subscription event as yang model into db for the #scenarios' () {
+ given: 'a blank data node that exist in db'
def blankDataNode = new DataNodeBuilder().withDataspace('NCMP-Admin')
.withAnchor('AVC-Subscriptions').withXpath('/subscription-registry').build()
- and: 'cps data service return non existing data node'
+ and: 'cps data service return an empty data node'
mockCpsDataService.getDataNodes(*_) >> [blankDataNode]
when: 'the yangModelSubscriptionEvent is saved into db'
objectUnderTest.saveSubscriptionEvent(yangModelSubscriptionEvent)
NO_TIMESTAMP)
}
- def 'update a subscription event' () {
- given: 'a data node exist in db'
+ def 'add or replace cm handle list element into db' () {
+ given: 'a data node with child node exist in db'
+ def leaves1 = [status:'PENDING', cmHandleId:'cmhandle1'] as Map
def childDataNode = new DataNodeBuilder().withDataspace('NCMP-Admin')
- .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription').build()
+ .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription')
+ .withLeaves(leaves1).build()
def engagedDataNode = new DataNodeBuilder().withDataspace('NCMP-Admin')
.withAnchor('AVC-Subscriptions').withXpath('/subscription-registry')
.withChildDataNodes([childDataNode]).build()
- and: 'cps data service return existing data node'
+ and: 'cps data service return data node including a child data node'
mockCpsDataService.getDataNodes(*_) >> [engagedDataNode]
- when: 'the yangModelSubscriptionEvent is saved into db'
+ and: 'cps data service return data node for querying by xpaths'
+ mockCpsDataService.getDataNodesForMultipleXpaths(*_) >> [engagedDataNode]
+ when: 'the yang model subscription event is saved into db'
objectUnderTest.saveSubscriptionEvent(yangModelSubscriptionEvent)
- then: 'the cpsDataService update operation is called with the correct data'
- 1 * mockCpsDataService.updateDataNodeAndDescendants(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
- SUBSCRIPTION_REGISTRY_PARENT,
- '{"subscription":[{' +
- '"topic":"some-topic",' +
- '"predicates":{"datastore":"some-datastore","targetCmHandles":[{"cmHandleId":"cmhandle1","status":"PENDING"},{"cmHandleId":"cmhandle2","status":"PENDING"}]},' +
- '"clientID":"some-client-id","subscriptionName":"some-subscription-name","isTagged":true}]}',
+ then: 'the cpsDataService save non-existing cm handle with the correct data'
+ 1 * mockCpsDataService.saveListElements(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+ SUBSCRIPTION_REGISTRY_PREDICATES_XPATH, '{"targetCmHandles":[{"cmHandleId":"cmhandle2","status":"PENDING"}]}',
+ NO_TIMESTAMP)
+ and: 'the cpsDataService update existing cm handle with the correct data'
+ 1 * mockCpsDataService.updateNodeLeaves(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+ SUBSCRIPTION_REGISTRY_PREDICATES_XPATH, '{"targetCmHandles":[{"cmHandleId":"cmhandle1","status":"PENDING"}]}',
NO_TIMESTAMP)
}