2  * ============LICENSE_START=======================================================
 
   4  * ================================================================================
 
   5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
 
   6  * Copyright © 2017-2018 Amdocs
 
   7  * ================================================================================
 
   8  * Licensed under the Apache License, Version 2.0 (the "License");
 
   9  * you may not use this file except in compliance with the License.
 
  10  * You may obtain a copy of the License at
 
  12  *       http://www.apache.org/licenses/LICENSE-2.0
 
  14  * Unless required by applicable law or agreed to in writing, software
 
  15  * distributed under the License is distributed on an "AS IS" BASIS,
 
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  17  * See the License for the specific language governing permissions and
 
  18  * limitations under the License.
 
  19  * ============LICENSE_END=========================================================
 
  21 package org.onap.aai.sparky.viewandinspect.services;
 
  23 import static java.util.concurrent.CompletableFuture.supplyAsync;
 
  25 import java.io.IOException;
 
  26 import java.util.List;
 
  28 import java.util.concurrent.ConcurrentHashMap;
 
  29 import java.util.concurrent.ExecutorService;
 
  30 import java.util.concurrent.atomic.AtomicInteger;
 
  32 import org.onap.aai.cl.api.Logger;
 
  33 import org.onap.aai.cl.eelf.LoggerFactory;
 
  34 import org.onap.aai.restclient.client.OperationResult;
 
  35 import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
 
  36 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
 
  37 import org.onap.aai.sparky.dal.GizmoAdapter;
 
  38 import org.onap.aai.sparky.logging.AaiUiMsgs;
 
  39 import org.onap.aai.sparky.sync.entity.SearchableEntity;
 
  40 import org.onap.aai.sparky.util.NodeUtils;
 
  41 import org.onap.aai.sparky.viewandinspect.config.SparkyConstants;
 
  42 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
 
  43 import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
 
  44 import org.onap.aai.sparky.viewandinspect.entity.GizmoEntity;
 
  45 import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipEntity;
 
  46 import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipHint;
 
  47 import org.onap.aai.sparky.viewandinspect.entity.InlineMessage;
 
  48 import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction;
 
  49 import org.onap.aai.sparky.viewandinspect.entity.QueryParams;
 
  50 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction;
 
  51 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState;
 
  52 import org.onap.aai.sparky.viewandinspect.task.PerformGizmoNodeSelfLinkProcessingTask;
 
  54 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 
  55 import com.fasterxml.jackson.databind.ObjectMapper;
 
  56 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
 
  59  * The Class SelfLinkNodeCollector.
 
  61 public class BaseGizmoVisualizationContext implements VisualizationContext {
 
  63   private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100;
 
  65   private static final Logger LOG =
 
  66       LoggerFactory.getInstance().getLogger(BaseGizmoVisualizationContext.class);
 
  68   private final GizmoAdapter gizmoAdapter;
 
  70   private AtomicInteger numLinksDiscovered;
 
  71   private AtomicInteger numSuccessfulLinkResolveFromCache;
 
  72   private AtomicInteger numSuccessfulLinkResolveFromFromServer;
 
  73   private AtomicInteger numFailedLinkResolve;
 
  74   private AtomicInteger aaiWorkOnHand;
 
  76   private VisualizationConfigs visualizationConfigs;
 
  78   private AtomicInteger totalLinksRetrieved;
 
  80   private final long contextId;
 
  81   private final String contextIdStr;
 
  82   private long lastProcessStatesSummaryLogInMs = -1;
 
  85   private ObjectMapper mapper;
 
  86   private InlineMessage inlineMessage = null;
 
  88   private ExecutorService graphExecutorService;
 
  89   private OxmEntityLookup oxmEntityLookup;
 
  90   private boolean rootNodeFound;
 
  93    * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
 
  94    * re-requesting the same self-links over-and-over again, to speed up the overall render time and
 
  95    * more importantly to reduce the network cost of determining information we already have.
 
  97   private ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
 
 100    * Instantiates a new self link node collector.
 
 102    * @param loader the loader
 
 103    * @throws Exception the exception
 
 105   public BaseGizmoVisualizationContext(long contextId, GizmoAdapter gizmoAdapter,
 
 106       ExecutorService graphExecutorService, VisualizationConfigs visualizationConfigs,
 
 107       OxmEntityLookup oxmEntityLookup) throws Exception {
 
 109     this.contextId = contextId;
 
 110     this.contextIdStr = "[Context-Id=" + contextId + "]";
 
 111     this.gizmoAdapter = gizmoAdapter;
 
 112     this.graphExecutorService = graphExecutorService;
 
 113     this.visualizationConfigs = visualizationConfigs;
 
 114     this.oxmEntityLookup = oxmEntityLookup;
 
 116     this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
 
 117     this.numLinksDiscovered = new AtomicInteger(0);
 
 118     this.totalLinksRetrieved = new AtomicInteger(0);
 
 119     this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
 
 120     this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
 
 121     this.numFailedLinkResolve = new AtomicInteger(0);
 
 122     this.aaiWorkOnHand = new AtomicInteger(0);
 
 124     this.mapper = new ObjectMapper();
 
 125     mapper.setSerializationInclusion(Include.NON_EMPTY);
 
 126     mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
 
 127     this.rootNodeFound = false;
 
 130   protected boolean isRootNodeFound() {
 
 131     return rootNodeFound;
 
 134   protected void setRootNodeFound(boolean rootNodeFound) {
 
 135     this.rootNodeFound = rootNodeFound;
 
 138   public long getContextId() {
 
 142   public GizmoAdapter getGizmoAdapter() {
 
 147    * Process self link response.
 
 149    * @param nodeId the node id
 
 151   private void processSelfLinkResponse(String nodeId) {
 
 153     if (nodeId == null) {
 
 154       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
 
 155           "Cannot process self link" + " response because nodeId is null");
 
 159     ActiveInventoryNode ain = nodeCache.get(nodeId);
 
 162       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
 
 163           "Cannot process self link response" + " because can't find node for id = " + nodeId);
 
 167     GizmoEntity gizmoEntity = null;
 
 170       gizmoEntity = mapper.readValue(ain.getOpResult().getResult(), GizmoEntity.class);
 
 171     } catch (Exception exc) {
 
 172       exc.printStackTrace();
 
 173       LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
 
 174           + " response str into JsonNode with error, " + exc.getLocalizedMessage());
 
 175       ain.changeState(NodeProcessingState.ERROR,
 
 176           NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
 
 180     if (gizmoEntity == null) {
 
 182       LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR,
 
 183           "Failed to parse json node str." + " Parse resulted a null value.");
 
 184       ain.changeState(NodeProcessingState.ERROR,
 
 185           NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
 
 190      * Now that we have the gizmo entity we can populate the AIN node with it, as well as the
 
 194     ain.setEntityType(gizmoEntity.getType());
 
 196     ain.setPrimaryKeyName(getEntityTypePrimaryKeyName(gizmoEntity.getType()));
 
 198     OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(gizmoEntity.getType());
 
 200     if (descriptor != null) {
 
 201       ain.setPrimaryKeyValue(getPrimaryKeyValues(gizmoEntity.getProperties(),
 
 202           descriptor.getPrimaryKeyAttributeNames()));
 
 204       LOG.error(AaiUiMsgs.ERROR_GENERIC, "Could not determine oxm descriptor for entity type = " + gizmoEntity.getType());
 
 207     gizmoEntity.getProperties().forEach((key, value) -> {
 
 208       ain.getProperties().put(key, value);
 
 211     // add edit attributes link
 
 212     if (ain.getSelfLink() != null) {
 
 213       ain.addProperty(SparkyConstants.URI_ATTR_NAME, ain.getSelfLink());
 
 217      * Only discover neighbors if our depth is less than the Max-Traversal-Depth
 
 220     if (ain.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
 
 223        * I think the next thing to do is:
 
 225        * 1. Calculate the source / target node id 2. Add the nodeId to the incoming / outgoing links
 
 226        * collection 3. Add the node to the node cache for processing
 
 229       String resourceLink = null;
 
 230       String relationshipNodeId = null;
 
 231       ActiveInventoryNode relationshipNode = null;
 
 233       for (GizmoRelationshipHint inRelationship : gizmoEntity.getIn()) {
 
 235         if (inRelationship.getSource() != null) {
 
 237           resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(inRelationship.getSource());
 
 238           relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink);
 
 240           if (!nodeCache.containsKey(relationshipNodeId)) {
 
 242             relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup);
 
 243             relationshipNode.setNodeId(relationshipNodeId);
 
 244             relationshipNode.setSelfLink(resourceLink);
 
 245             relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
 
 246                 NodeProcessingAction.NEW_NODE_PROCESSED);
 
 248             ain.addInboundNeighbor(relationshipNodeId);
 
 250             addNode(relationshipNode);
 
 257       for (GizmoRelationshipHint outRelationship : gizmoEntity.getOut()) {
 
 259         if (outRelationship.getTarget() != null) {
 
 261           resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(outRelationship.getTarget());
 
 262           relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink);
 
 264           if (!nodeCache.containsKey(relationshipNodeId)) {
 
 266             relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup);
 
 267             relationshipNode.setNodeId(relationshipNodeId);
 
 268             relationshipNode.setSelfLink(resourceLink);
 
 269             relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
 
 270                 NodeProcessingAction.NEW_NODE_PROCESSED);
 
 272             ain.addOutboundNeighbor(relationshipNodeId);
 
 274             addNode(relationshipNode);
 
 282     ain.changeState(NodeProcessingState.READY, NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
 
 287    * Perform self link resolve.
 
 289    * @param nodeId the node id
 
 291   private void performSelfLinkResolve(String nodeId) {
 
 293     if (nodeId == null) {
 
 294       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
 
 295           "Resolve of self-link" + " has been skipped because provided nodeId is null");
 
 299     ActiveInventoryNode ain = nodeCache.get(nodeId);
 
 302       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId
 
 303           + ", from node cache. Resolve self-link method has been skipped.");
 
 307     if (!ain.isSelfLinkPendingResolve()) {
 
 309       ain.setSelfLinkPendingResolve(true);
 
 311       // kick off async self-link resolution
 
 313       if (LOG.isDebugEnabled()) {
 
 314         LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
 
 315             "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink());
 
 318       numLinksDiscovered.incrementAndGet();
 
 321        * If the current node is the search target, we want to see everything the node has to offer
 
 322        * from the self-link and not filter it to a single node.
 
 325       NodeProcessingTransaction txn = new NodeProcessingTransaction();
 
 326       txn.setProcessingNode(ain);
 
 327       txn.setRequestParameters(null);
 
 328       aaiWorkOnHand.incrementAndGet();
 
 329       supplyAsync(new PerformGizmoNodeSelfLinkProcessingTask(txn, null, gizmoAdapter),
 
 330           graphExecutorService).whenComplete((nodeTxn, error) -> {
 
 335                * an error processing the self link should probably result in the node processing
 
 336                * state shifting to ERROR
 
 339               nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
 
 341               nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
 
 342                   NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
 
 344               nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
 
 348               totalLinksRetrieved.incrementAndGet();
 
 350               OperationResult opResult = nodeTxn.getOpResult();
 
 352               if (opResult != null && opResult.wasSuccessful()) {
 
 354                 if (!opResult.wasSuccessful()) {
 
 355                   numFailedLinkResolve.incrementAndGet();
 
 358                 if (opResult.isFromCache()) {
 
 359                   numSuccessfulLinkResolveFromCache.incrementAndGet();
 
 361                   numSuccessfulLinkResolveFromFromServer.incrementAndGet();
 
 365                 nodeTxn.getProcessingNode().setOpResult(opResult);
 
 366                 nodeTxn.getProcessingNode().changeState(
 
 367                     NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
 
 368                     NodeProcessingAction.SELF_LINK_RESOLVE_OK);
 
 370                 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
 
 371                 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
 
 374                 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
 
 375                     "Self Link retrieval for link," + txn.getSelfLinkWithModifiers()
 
 376                         + ", failed with error code," + nodeTxn.getOpResult().getResultCode()
 
 377                         + ", and message," + nodeTxn.getOpResult().getResult());
 
 379                 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
 
 380                 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
 
 382                 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
 
 383                     NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
 
 385                 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
 
 390             aaiWorkOnHand.decrementAndGet();
 
 398   public GizmoRelationshipEntity getGizmoRelationshipEntity(String gizmoJsonResponse) {
 
 400     GizmoRelationshipEntity gizmoRelationship = null;
 
 402       gizmoRelationship = mapper.readValue(gizmoJsonResponse, GizmoRelationshipEntity.class);
 
 403     } catch (IOException exc) {
 
 404       LOG.error(AaiUiMsgs.ERROR_GENERIC, "Failed to map json to GizmoRelationshipEntity.  Error: " + exc.getMessage());
 
 407     return gizmoRelationship;
 
 411   public String getPrimaryKeyValues(Map<String, String> properties, List<String> pkeyNames) {
 
 413     StringBuilder sb = new StringBuilder(64);
 
 415     if (pkeyNames.size() > 0) {
 
 416       String primaryKey = properties.get(pkeyNames.get(0));
 
 417       if (primaryKey != null) {
 
 418         sb.append(primaryKey);
 
 420         // this should be a fatal error because unless we can
 
 421         // successfully retrieve all the expected keys we'll end up
 
 422         // with a garbage node
 
 423         LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract" + " keyName, "
 
 424             + pkeyNames.get(0) + ", from properties , " + properties);
 
 428       for (int i = 1; i < pkeyNames.size(); i++) {
 
 430         String kv = properties.get(pkeyNames.get(i));
 
 432           sb.append("/").append(kv);
 
 434           // this should be a fatal error because unless we can
 
 435           // successfully retrieve all the expected keys we'll end up
 
 436           // with a garbage node
 
 437           LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR:  failed to extract keyName, "
 
 438               + pkeyNames.get(i) + ", from properties, " + properties);
 
 443       return sb.toString();
 
 454    * Find and mark root node.
 
 456    * @param queryParams the query params
 
 457    * @return true, if successful
 
 459   private void findAndMarkRootNode(QueryParams queryParams) {
 
 461     if (isRootNodeFound()) {
 
 465     for (ActiveInventoryNode cacheNode : nodeCache.values()) {
 
 467       if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
 
 468         cacheNode.setNodeDepth(0);
 
 469         cacheNode.setRootNode(true);
 
 470         LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
 
 471         setRootNodeFound(true);
 
 477   public void addNode(ActiveInventoryNode node) {
 
 483     nodeCache.putIfAbsent(node.getNodeId(), node);
 
 486   public VisualizationConfigs getVisualizationConfigs() {
 
 487     return visualizationConfigs;
 
 490   public void setVisualizationConfigs(VisualizationConfigs visualizationConfigs) {
 
 491     this.visualizationConfigs = visualizationConfigs;
 
 494   public OxmEntityLookup getOxmEntityLookup() {
 
 495     return oxmEntityLookup;
 
 498   public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) {
 
 499     this.oxmEntityLookup = oxmEntityLookup;
 
 502   public ObjectMapper getMapper() {
 
 506   public void setMapper(ObjectMapper mapper) {
 
 507     this.mapper = mapper;
 
 510   private void dumpThrottledWorkOnHandLog() {
 
 511     dumpThrottledWorkOnHandLog(false);
 
 514   private void dumpThrottledWorkOnHandLog(boolean override) {
 
 516     if ((lastProcessStatesSummaryLogInMs < 0)
 
 517         || ((System.currentTimeMillis() > (lastProcessStatesSummaryLogInMs + 5000))) || override) {
 
 519       lastProcessStatesSummaryLogInMs = System.currentTimeMillis();
 
 524       int numSelfLinkUnresolved = 0;
 
 525       int numSelfLinkResponseUnprocessed = 0;
 
 527       for (ActiveInventoryNode cacheNode : nodeCache.values()) {
 
 529         switch (cacheNode.getState()) {
 
 545           case SELF_LINK_UNRESOLVED: {
 
 546             numSelfLinkUnresolved++;
 
 550           case SELF_LINK_RESPONSE_UNPROCESSED: {
 
 551             numSelfLinkResponseUnprocessed++;
 
 561       LOG.info(AaiUiMsgs.INFO_GENERIC,
 
 563               "ProcessCurrentStates for ContextId=%s, [PendingTxns=%d, numInit=%d, numSelfLinkUnresolved=%d, numSelfLinkResponseUnProcessed=%d, numReady=%d, numError=%d]",
 
 564               contextIdStr, aaiWorkOnHand.get(), numInit, numSelfLinkUnresolved, numSelfLinkResponseUnprocessed,
 
 565               numReady, numError));
 
 571    * Process current node states.
 
 573    * @param rootNodeDiscovered the root node discovered
 
 575   private void processCurrentNodeStates(QueryParams queryParams) {
 
 577      * Force an evaluation of node depths before determining if we should limit state-based
 
 578      * traversal or processing.
 
 581     findAndMarkRootNode(queryParams);
 
 583     verifyOutboundNeighbors();
 
 585     for (ActiveInventoryNode cacheNode : nodeCache.values()) {
 
 587       if (LOG.isDebugEnabled()) {
 
 588         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "processCurrentNodeState(), nid = "
 
 589             + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
 
 592       switch (cacheNode.getState()) {
 
 595           processInitialState(cacheNode.getNodeId());
 
 604         case SELF_LINK_UNRESOLVED: {
 
 605           performSelfLinkResolve(cacheNode.getNodeId());
 
 609         case SELF_LINK_RESPONSE_UNPROCESSED: {
 
 610           processSelfLinkResponse(cacheNode.getNodeId());
 
 620     dumpThrottledWorkOnHandLog();
 
 626   public int getNumSuccessfulLinkResolveFromCache() {
 
 627     return numSuccessfulLinkResolveFromCache.get();
 
 630   public int getNumSuccessfulLinkResolveFromFromServer() {
 
 631     return numSuccessfulLinkResolveFromFromServer.get();
 
 634   public int getNumFailedLinkResolve() {
 
 635     return numFailedLinkResolve.get();
 
 638   public InlineMessage getInlineMessage() {
 
 639     return inlineMessage;
 
 642   public void setInlineMessage(InlineMessage inlineMessage) {
 
 643     this.inlineMessage = inlineMessage;
 
 646   public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
 
 653    * Process initial state.
 
 655    * @param nodeId the node id
 
 657   private void processInitialState(String nodeId) {
 
 659     if (nodeId == null) {
 
 660       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
 
 664     ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
 
 666     if (cachedNode == null) {
 
 667       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE,
 
 668           "Node cannot be" + " found for nodeId, " + nodeId);
 
 672     if (cachedNode.getSelfLink() == null) {
 
 674       if (cachedNode.getNodeId() == null) {
 
 677          * if the self link is null at the INIT state, which could be valid if this node is a
 
 678          * complex attribute group which didn't originate from a self-link, but in that situation
 
 679          * both the node id and node key should already be set.
 
 682         cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
 
 686       if (cachedNode.getNodeId() != null) {
 
 689          * This should be the success path branch if the self-link is not set
 
 692         cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
 
 693             NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
 
 699       if (cachedNode.hasResolvedSelfLink()) {
 
 700         LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
 
 701         cachedNode.changeState(NodeProcessingState.ERROR,
 
 702             NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
 
 704         cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
 
 705             NodeProcessingAction.SELF_LINK_SET);
 
 711    * Process skeleton node.
 
 713    * @param skeletonNode the skeleton node
 
 714    * @param queryParams the query params
 
 716   private void processSearchableEntity(SearchableEntity searchTargetEntity,
 
 717       QueryParams queryParams) {
 
 719     if (searchTargetEntity == null) {
 
 723     if (searchTargetEntity.getId() == null) {
 
 724       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
 
 725           + " node because nodeId is null for node, " + searchTargetEntity.getLink());
 
 729     ActiveInventoryNode newNode =
 
 730         new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
 
 732     newNode.setNodeId(searchTargetEntity.getId());
 
 734     newNode.setNodeDepth(0);
 
 735     newNode.setRootNode(true);
 
 736     LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
 
 737     setRootNodeFound(true);
 
 739     newNode.setSelfLink(searchTargetEntity.getLink());
 
 741     nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
 
 744   private int getTotalWorkOnHand() {
 
 746     int numNodesWithPendingStates = 0;
 
 748     if (isRootNodeFound()) {
 
 749       evaluateNodeDepths();
 
 752     for (ActiveInventoryNode n : nodeCache.values()) {
 
 754       switch (n.getState()) {
 
 758           // do nothing, these are our normal
 
 766            * for all other states, there is work to be done
 
 768           numNodesWithPendingStates++;
 
 775     return (aaiWorkOnHand.get() + numNodesWithPendingStates);
 
 780    * Checks for out standing work.
 
 782    * @return true, if successful
 
 784   private void processOutstandingWork(QueryParams queryParams) {
 
 786     while (getTotalWorkOnHand() > 0) {
 
 789        * Force an evaluation of node depths before determining if we should limit state-based
 
 790        * traversal or processing.
 
 793       processCurrentNodeStates(queryParams);
 
 797       } catch (InterruptedException exc) {
 
 798         LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
 
 799         Thread.currentThread().interrupt();
 
 805     dumpThrottledWorkOnHandLog(true);
 
 813    * org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai.
 
 814    * sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams)
 
 817   public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
 
 822       if (searchtargetEntity == null) {
 
 823         LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
 
 824             contextIdStr + " - Failed to" + " processSelfLinks, searchtargetEntity is null");
 
 828       long startTimeInMs = System.currentTimeMillis();
 
 830       processSearchableEntity(searchtargetEntity, queryParams);
 
 833        * This method is blocking until we decouple it with a CountDownLatch await condition, and
 
 834        * make the internal graph processing more event-y.
 
 837       processOutstandingWork(queryParams);
 
 839       long totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
 
 841       long opTime = System.currentTimeMillis() - startTimeInMs;
 
 843       LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
 
 844           String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
 
 846     } catch (Exception exc) {
 
 847       LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
 
 853    * Verify outbound neighbors.
 
 855   private void verifyOutboundNeighbors() {
 
 857     for (ActiveInventoryNode srcNode : nodeCache.values()) {
 
 859       for (String targetNodeId : srcNode.getOutboundNeighbors()) {
 
 861         ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
 
 863         if (targetNode != null && srcNode.getNodeId() != null) {
 
 865           targetNode.addInboundNeighbor(srcNode.getNodeId());
 
 867           if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
 
 868             targetNode.addOutboundNeighbor(srcNode.getNodeId());
 
 880    * Evaluate node depths.
 
 882   private void evaluateNodeDepths() {
 
 887     while (numChanged != 0) {
 
 892       for (ActiveInventoryNode srcNode : nodeCache.values()) {
 
 894         if (srcNode.getState() == NodeProcessingState.INIT) {
 
 897            * this maybe the only state that we don't want to to process the node depth on, because
 
 898            * typically it won't have any valid fields set, and it may remain in a partial state
 
 899            * until we have processed the self-link.
 
 906         for (String targetNodeId : srcNode.getOutboundNeighbors()) {
 
 907           ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
 
 909           if (targetNode != null) {
 
 911             if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
 
 917         for (String targetNodeId : srcNode.getInboundNeighbors()) {
 
 918           ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
 
 920           if (targetNode != null) {
 
 922             if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
 
 929       if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
 
 930         LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
 
 936     if (LOG.isDebugEnabled()) {
 
 937       if (numAttempts > 0) {
 
 938         LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
 
 939             "Evaluate node depths completed in " + numAttempts + " attempts");
 
 941         LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
 
 942             "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
 
 950    * Gets the entity type primary key name.
 
 952    * @param entityType the entity type
 
 953    * @return the entity type primary key name
 
 957   private String getEntityTypePrimaryKeyName(String entityType) {
 
 959     if (entityType == null) {
 
 960       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
 
 961           "node primary key" + " name because entity type is null");
 
 965     OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
 
 967     if (descriptor == null) {
 
 968       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
 
 969           "oxm entity" + " descriptor for entityType = " + entityType);
 
 973     List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
 
 975     if (pkeyNames == null || pkeyNames.size() == 0) {
 
 976       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
 
 977           "node primary" + " key because descriptor primary key names is empty");
 
 981     return NodeUtils.concatArray(pkeyNames, "/");