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.net.URISyntaxException;
 
  26 import java.util.ArrayList;
 
  27 import java.util.Collection;
 
  28 import java.util.Iterator;
 
  29 import java.util.List;
 
  31 import java.util.Map.Entry;
 
  32 import java.util.concurrent.ConcurrentHashMap;
 
  33 import java.util.concurrent.ExecutorService;
 
  34 import java.util.concurrent.atomic.AtomicInteger;
 
  36 import org.apache.http.client.utils.URIBuilder;
 
  37 import org.onap.aai.cl.api.Logger;
 
  38 import org.onap.aai.cl.eelf.LoggerFactory;
 
  39 import org.onap.aai.restclient.client.OperationResult;
 
  40 import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
 
  41 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
 
  42 import org.onap.aai.sparky.dal.ActiveInventoryAdapter;
 
  43 import org.onap.aai.sparky.logging.AaiUiMsgs;
 
  44 import org.onap.aai.sparky.sync.entity.SearchableEntity;
 
  45 import org.onap.aai.sparky.util.NodeUtils;
 
  46 import org.onap.aai.sparky.viewandinspect.config.SparkyConstants;
 
  47 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
 
  48 import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
 
  49 import org.onap.aai.sparky.viewandinspect.entity.InlineMessage;
 
  50 import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction;
 
  51 import org.onap.aai.sparky.viewandinspect.entity.QueryParams;
 
  52 import org.onap.aai.sparky.viewandinspect.entity.Relationship;
 
  53 import org.onap.aai.sparky.viewandinspect.entity.RelationshipData;
 
  54 import org.onap.aai.sparky.viewandinspect.entity.RelationshipList;
 
  55 import org.onap.aai.sparky.viewandinspect.entity.SelfLinkDeterminationTransaction;
 
  56 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction;
 
  57 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState;
 
  58 import org.onap.aai.sparky.viewandinspect.task.PerformNodeSelfLinkProcessingTask;
 
  59 import org.onap.aai.sparky.viewandinspect.task.PerformSelfLinkDeterminationTask;
 
  61 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 
  62 import com.fasterxml.jackson.databind.JsonNode;
 
  63 import com.fasterxml.jackson.databind.ObjectMapper;
 
  64 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
 
  67  * The Class SelfLinkNodeCollector.
 
  69 public class BaseVisualizationContext implements VisualizationContext {
 
  71   private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100;
 
  72   private static final String DEPTH_ALL_MODIFIER = "?depth=all";
 
  73   private static final String NODES_ONLY_MODIFIER = "?nodes-only";
 
  74   private static final String SERVICE_INSTANCE = "service-instance";
 
  76   private static final Logger LOG = LoggerFactory.getInstance().getLogger(
 
  77       BaseVisualizationContext.class);
 
  78   private final ActiveInventoryAdapter aaiAdapter;
 
  80   private int maxSelfLinkTraversalDepth;
 
  81   private AtomicInteger numLinksDiscovered;
 
  82   private AtomicInteger numSuccessfulLinkResolveFromCache;
 
  83   private AtomicInteger numSuccessfulLinkResolveFromFromServer;
 
  84   private AtomicInteger numFailedLinkResolve;
 
  85   private AtomicInteger aaiWorkOnHand;
 
  87   private VisualizationConfigs visualizationConfigs;
 
  89   private AtomicInteger totalLinksRetrieved;
 
  91   private final long contextId;
 
  92   private final String contextIdStr;
 
  94   private ObjectMapper mapper;
 
  95   private InlineMessage inlineMessage = null;
 
  97   private ExecutorService aaiExecutorService;
 
  98   private OxmEntityLookup oxmEntityLookup;
 
  99   private boolean rootNodeFound;
 
 102    * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
 
 103    * re-requesting the same self-links over-and-over again, to speed up the overall render time and
 
 104    * more importantly to reduce the network cost of determining information we already have.
 
 106   private ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
 
 109    * Instantiates a new self link node collector.
 
 111    * @param loader the loader
 
 112    * @throws Exception the exception
 
 114   public BaseVisualizationContext(long contextId, ActiveInventoryAdapter aaiAdapter,
 
 115       ExecutorService aaiExecutorService, VisualizationConfigs visualizationConfigs,
 
 116       OxmEntityLookup oxmEntityLookup)
 
 119     this.contextId = contextId;
 
 120     this.contextIdStr = "[Context-Id=" + contextId + "]";
 
 121     this.aaiAdapter = aaiAdapter;
 
 122     this.aaiExecutorService = aaiExecutorService;
 
 123     this.visualizationConfigs = visualizationConfigs;
 
 124     this.oxmEntityLookup = oxmEntityLookup;
 
 126     this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
 
 127     this.numLinksDiscovered = new AtomicInteger(0);
 
 128     this.totalLinksRetrieved = new AtomicInteger(0);
 
 129     this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
 
 130     this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
 
 131     this.numFailedLinkResolve = new AtomicInteger(0);
 
 132     this.aaiWorkOnHand = new AtomicInteger(0);
 
 134     this.maxSelfLinkTraversalDepth = this.visualizationConfigs.getMaxSelfLinkTraversalDepth();
 
 136     this.mapper = new ObjectMapper();
 
 137     mapper.setSerializationInclusion(Include.NON_EMPTY);
 
 138     mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
 
 139     this.rootNodeFound = false;
 
 142   protected boolean isRootNodeFound() {
 
 143     return rootNodeFound;
 
 146   protected void setRootNodeFound(boolean rootNodeFound) {
 
 147     this.rootNodeFound = rootNodeFound;
 
 150   public long getContextId() {
 
 155    * A utility method for extracting all entity-type primary key values from a provided self-link
 
 156    * and return a set of generic-query API keys.
 
 158    * @param parentEntityType
 
 160    * @return a list of key values that can be used for this entity with the AAI generic-query API
 
 162   protected List<String> extractQueryParamsFromSelfLink(String link) {
 
 164     List<String> queryParams = new ArrayList<String>();
 
 167       LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, "self link is null");
 
 171     Map<String, OxmEntityDescriptor> entityDescriptors = oxmEntityLookup.getEntityDescriptors();
 
 175       URIBuilder urlBuilder = new URIBuilder(link);
 
 176       String urlPath = urlBuilder.getPath();
 
 178       OxmEntityDescriptor descriptor = null;
 
 179       String[] urlPathElements = urlPath.split("/");
 
 180       List<String> primaryKeyNames = null;
 
 182       String entityType = null;
 
 184       while (index < urlPathElements.length) {
 
 186         descriptor = entityDescriptors.get(urlPathElements[index]);
 
 188         if (descriptor != null) {
 
 189           entityType = urlPathElements[index];
 
 190           primaryKeyNames = descriptor.getPrimaryKeyAttributeNames();
 
 193            * Make sure from what ever index we matched the parent entity-type on that we can extract
 
 194            * additional path elements for the primary key values.
 
 197           if (index + primaryKeyNames.size() < urlPathElements.length) {
 
 199             for (String primaryKeyName : primaryKeyNames) {
 
 201               queryParams.add(entityType + "." + primaryKeyName + ":" + urlPathElements[index]);
 
 204             LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
 
 205                 "Could not extract query parametrs for entity-type = '" + entityType
 
 206                     + "' from self-link = " + link);
 
 213     } catch (URISyntaxException exc) {
 
 215       LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
 
 216           "Error extracting query parameters from self-link = " + link + ". Error = "
 
 225    * Decode complex attribute group.
 
 228    * @param attributeGroup the attribute group
 
 229    * @return boolean indicating whether operation was successful (true), / failure(false).
 
 231   public boolean decodeComplexAttributeGroup(ActiveInventoryNode ain, JsonNode attributeGroup) {
 
 235       Iterator<Entry<String, JsonNode>> entityArrays = attributeGroup.fields();
 
 236       Entry<String, JsonNode> entityArray = null;
 
 238       if (entityArrays == null) {
 
 239         LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, attributeGroup.toString());
 
 240         ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
 
 244       while (entityArrays.hasNext()) {
 
 246         entityArray = entityArrays.next();
 
 248         String entityType = entityArray.getKey();
 
 249         JsonNode entityArrayObject = entityArray.getValue();
 
 251         if (entityArrayObject.isArray()) {
 
 253           Iterator<JsonNode> entityCollection = entityArrayObject.elements();
 
 254           JsonNode entity = null;
 
 255           while (entityCollection.hasNext()) {
 
 256             entity = entityCollection.next();
 
 258             if (LOG.isDebugEnabled()) {
 
 259               LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "decodeComplexAttributeGroup(),"
 
 260                   + " entity = " + entity.toString());
 
 264              * Here's what we are going to do:
 
 266              * <li>In the ActiveInventoryNode, on construction maintain a collection of queryParams
 
 267              * that is added to for the purpose of discovering parent->child hierarchies.
 
 269              * <li>When we hit this block of the code then we'll use the queryParams to feed the
 
 270              * generic query to resolve the self-link asynchronously.
 
 272              * <li>Upon successful link determination, then and only then will we create a new node
 
 273              * in the nodeCache and process the child
 
 277             ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
 
 278             newNode.setEntityType(entityType);
 
 281              * This is partially a lie because we actually don't have a self-link for complex nodes
 
 282              * discovered in this way.
 
 284             newNode.setSelfLinkProcessed(true);
 
 285             newNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
 
 286                 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
 
 289              * copy parent query params into new child
 
 292             if (SERVICE_INSTANCE.equals(entityType)) {
 
 295                * 1707 AAI has an issue being tracked with AAI-8932 where the generic-query cannot be
 
 296                * resolved if all the service-instance path keys are provided. The query only works
 
 297                * if only the service-instance key and valude are passed due to a historical reason.
 
 298                * A fix is being worked on for 1707, and when it becomes available we can revert this
 
 302               newNode.clearQueryParams();
 
 307                * For all other entity-types we want to copy the parent query parameters into the new node
 
 311               for (String queryParam : ain.getQueryParams()) {
 
 312                 newNode.addQueryParam(queryParam);
 
 318             if (!addComplexGroupToNode(newNode, entity)) {
 
 319               LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, "Failed to add child to parent for child = " +  entity.toString());
 
 322             if (!addNodeQueryParams(newNode)) {
 
 323               LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Error determining node id and key for node = " + newNode.dumpNodeTree(true)
 
 324                   + " skipping relationship processing");
 
 325               newNode.changeState(NodeProcessingState.ERROR,
 
 326                   NodeProcessingAction.NODE_IDENTITY_ERROR);
 
 330               newNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
 
 331                   NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
 
 337              * Order matters for the query params. We need to set the parent ones before the child
 
 341             String selfLinkQuery =
 
 342                 aaiAdapter.getGenericQueryForSelfLink(entityType, newNode.getQueryParams());
 
 345              * <li>get the self-link
 
 346              * <li>add it to the new node
 
 347              * <li>generate node id
 
 348              * <li>add node to node cache
 
 349              * <li>add node id to parent outbound links list
 
 350              * <li>process node children (should be automatic) (but don't query and resolve
 
 351              * self-link as we already have all the data)
 
 354             SelfLinkDeterminationTransaction txn = new SelfLinkDeterminationTransaction();
 
 356             txn.setQueryString(selfLinkQuery);
 
 357             txn.setNewNode(newNode);
 
 358             txn.setParentNodeId(ain.getNodeId());
 
 359             aaiWorkOnHand.incrementAndGet();
 
 360             supplyAsync(new PerformSelfLinkDeterminationTask(txn, null, aaiAdapter),
 
 361                 aaiExecutorService).whenComplete((nodeTxn, error) -> {
 
 364                     LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_GENERIC, selfLinkQuery);
 
 367                     OperationResult opResult = nodeTxn.getOpResult();
 
 369                     ActiveInventoryNode newChildNode = txn.getNewNode();
 
 371                     if (opResult != null && opResult.wasSuccessful()) {
 
 373                       if (!opResult.wasSuccessful()) {
 
 374                         numFailedLinkResolve.incrementAndGet();
 
 377                       if (opResult.isFromCache()) {
 
 378                         numSuccessfulLinkResolveFromCache.incrementAndGet();
 
 380                         numSuccessfulLinkResolveFromFromServer.incrementAndGet();
 
 384                        * extract the self-link from the operational result.
 
 387                       Collection<JsonNode> entityLinks = new ArrayList<JsonNode>();
 
 388                       JsonNode genericQueryResult = null;
 
 391                             NodeUtils.convertJsonStrToJsonNode(nodeTxn.getOpResult().getResult());
 
 392                       } catch (Exception exc) {
 
 393                         LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, JsonNode.class.toString(), exc.getMessage());
 
 396                       NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link",
 
 399                       String selfLink = null;
 
 401                       if (entityLinks.size() != 1) {
 
 403                         LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS, String.valueOf(entityLinks.size()));
 
 406                         selfLink = ((JsonNode) entityLinks.toArray()[0]).asText();
 
 407                         selfLink = ActiveInventoryAdapter.extractResourcePath(selfLink);
 
 409                         newChildNode.setSelfLink(selfLink);
 
 410                         newChildNode.setNodeId(NodeUtils.generateUniqueShaDigest(selfLink));
 
 412                         String uri = NodeUtils.calculateEditAttributeUri(selfLink);
 
 414                           newChildNode.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
 
 417                         ActiveInventoryNode parent = nodeCache.get(txn.getParentNodeId());
 
 419                         if (parent != null) {
 
 420                           parent.addOutboundNeighbor(newChildNode.getNodeId());
 
 421                           newChildNode.addInboundNeighbor(parent.getNodeId());
 
 424                         newChildNode.setSelfLinkPendingResolve(false);
 
 425                         newChildNode.setSelfLinkProcessed(true);
 
 426                         newChildNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
 
 427                               NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
 
 429                         nodeCache.putIfAbsent(newChildNode.getNodeId(), newChildNode);
 
 434                       LOG.error(AaiUiMsgs.SELF_LINK_RETRIEVAL_FAILED, txn.getQueryString(),
 
 435                           String.valueOf(nodeTxn.getOpResult().getResultCode()), nodeTxn.getOpResult().getResult());
 
 436                       newChildNode.setSelflinkRetrievalFailure(true);
 
 437                       newChildNode.setSelfLinkProcessed(true);
 
 438                       newChildNode.setSelfLinkPendingResolve(false);
 
 440                       newChildNode.changeState(NodeProcessingState.ERROR,
 
 441                           NodeProcessingAction.SELF_LINK_DETERMINATION_ERROR);
 
 447                   aaiWorkOnHand.decrementAndGet();
 
 456           LOG.error(AaiUiMsgs.UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE, entityType);
 
 460     } catch (Exception exc) {
 
 461       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Exception caught while"
 
 462           + " decoding complex attribute group - " + exc.getMessage());
 
 470    * Process self link response.
 
 472    * @param nodeId the node id
 
 474   private void processSelfLinkResponse(String nodeId) {
 
 476     if (nodeId == null) {
 
 477       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link"
 
 478           + " response because nodeId is null");
 
 482     ActiveInventoryNode ain = nodeCache.get(nodeId);
 
 485       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link response"
 
 486           + " because can't find node for id = " + nodeId);
 
 490     JsonNode jsonNode = null;
 
 493       jsonNode = mapper.readValue(ain.getOpResult().getResult(), JsonNode.class);
 
 494     } catch (Exception exc) {
 
 495       LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
 
 496           + " response str into JsonNode with error, " + exc.getLocalizedMessage());
 
 497       ain.changeState(NodeProcessingState.ERROR,
 
 498           NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
 
 502     if (jsonNode == null) {
 
 503       LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse json node str."
 
 504           + " Parse resulted a null value.");
 
 505       ain.changeState(NodeProcessingState.ERROR,
 
 506           NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
 
 510     Iterator<Entry<String, JsonNode>> fieldNames = jsonNode.fields();
 
 511     Entry<String, JsonNode> field = null;
 
 513     RelationshipList relationshipList = null;
 
 515     while (fieldNames.hasNext()) {
 
 517       field = fieldNames.next();
 
 518       String fieldName = field.getKey();
 
 520       if ("relationship-list".equals(fieldName)) {
 
 523           relationshipList = mapper.readValue(field.getValue().toString(), RelationshipList.class);
 
 525           if (relationshipList != null) {
 
 526             ain.addRelationshipList(relationshipList);
 
 529         } catch (Exception exc) {
 
 530           LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse relationship-list"
 
 531               + " attribute. Parse resulted in error, " + exc.getLocalizedMessage());
 
 532           ain.changeState(NodeProcessingState.ERROR,
 
 533               NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
 
 539         JsonNode nodeValue = field.getValue();
 
 541         if(nodeValue!=null) {
 
 542           if (nodeValue.isValueNode()) {
 
 543             String key = fieldName;
 
 544             handleNodeValue(ain, fieldName, key, nodeValue.asText());
 
 545           } else if (nodeValue.isArray()) {
 
 546             String key = field.getKey();
 
 547             handleNodeValue(ain, fieldName, key, nodeValue.toString());
 
 549               ain.addComplexGroup(nodeValue);
 
 557     String uri = NodeUtils.calculateEditAttributeUri(ain.getSelfLink());
 
 559       ain.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
 
 563      * We need a special behavior for intermediate entities from the REST model
 
 565      * Tenants are not top level entities, and when we want to visualization
 
 566      * their children, we need to construct keys that include the parent entity query
 
 567      * keys, the current entity type keys, and the child keys.   We'll always have the
 
 568      * current entity and children, but never the parent entity in the current (1707) REST
 
 571      * We have two possible solutions:
 
 573      * 1) Try to use the custom-query approach to learn about the entity keys
 
 574      *    - this could be done, but it could be very expensive for large objects.  When we do the first
 
 575      *      query to get a tenant, it will list all the in and out edges related to this entity,
 
 576      *      there is presently no way to filter this.  But the approach could be made to work and it would be
 
 577      *      somewhat data-model driven, other than the fact that we have to first realize that the entity
 
 578      *      that is being searched for is not top-level entity.  Once we have globally unique ids for resources
 
 579      *      this logic will not be needed and everything will be simpler.   The only reason we are in this logic
 
 580      *      at all is to be able to calculate a url for the child entities so we can hash it to generate 
 
 581      *      a globally unique id that can be safely used for the node.
 
 583      * *2* Extract the keys from the pathed self-link.
 
 584      *     This is a bad solution and I don't like it but it will be fast for all resource types, as the 
 
 585      *     information is already encoded in the URI.   When we get to a point where we switch to a better
 
 586      *     globally unique entity identity model, then a lot of the code being used to calculate an entity url
 
 587      *     to in-turn generate a deterministic globally unique id will disappear.      
 
 590      * right now we have the following:
 
 592      * - cloud-regions/cloud-region/{cloud-region-id}/{cloud-owner-id}/tenants/tenant/{tenant-id}
 
 597      * For all entity types use the self-link extraction method to be consistent.  Once we have a
 
 598      * globally unique identity mechanism for entities, this logic can be revisited.
 
 600     ain.clearQueryParams();
 
 601     ain.addQueryParams(extractQueryParamsFromSelfLink(ain.getSelfLink()));
 
 602       ain.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
 
 603           NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
 
 608   private void handleNodeValue(ActiveInventoryNode ain, String fieldName, String key, String value) {
 
 609     if (oxmEntityLookup.getEntityDescriptors().get(fieldName) == null) {
 
 612        * entity property name is not an entity, thus we can add this property name and value
 
 613        * to our property set
 
 616       ain.addProperty(key, value);
 
 622    * Perform self link resolve.
 
 624    * @param nodeId the node id
 
 626   private void performSelfLinkResolve(String nodeId) {
 
 628     if (nodeId == null) {
 
 629       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Resolve of self-link"
 
 630           + " has been skipped because provided nodeId is null");
 
 634     ActiveInventoryNode ain = nodeCache.get(nodeId);
 
 637       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId
 
 638           + ", from node cache. Resolve self-link method has been skipped.");
 
 642     if (!ain.isSelfLinkPendingResolve()) {
 
 644       ain.setSelfLinkPendingResolve(true);
 
 646       // kick off async self-link resolution
 
 648       if (LOG.isDebugEnabled()) {
 
 649         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
 650             "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink());
 
 653       numLinksDiscovered.incrementAndGet();
 
 655       String depthModifier = DEPTH_ALL_MODIFIER;
 
 658        * If the current node is the search target, we want to see everything the node has to offer
 
 659        * from the self-link and not filter it to a single node.
 
 662       if (visualizationConfigs.getShallowEntities().contains(ain.getEntityType())
 
 663           && !ain.isRootNode()) {
 
 664         depthModifier = NODES_ONLY_MODIFIER;
 
 667       NodeProcessingTransaction txn = new NodeProcessingTransaction();
 
 668       txn.setProcessingNode(ain);
 
 669       txn.setRequestParameters(depthModifier);
 
 670       aaiWorkOnHand.incrementAndGet();
 
 672           new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiAdapter),
 
 673           aaiExecutorService).whenComplete((nodeTxn, error) -> {
 
 678                * an error processing the self link should probably result in the node processing
 
 679                * state shifting to ERROR
 
 682               nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
 
 684               nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
 
 685                   NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
 
 687               nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
 
 691               totalLinksRetrieved.incrementAndGet();
 
 693               OperationResult opResult = nodeTxn.getOpResult();
 
 695               if (opResult != null && opResult.wasSuccessful()) {
 
 697                 if (!opResult.wasSuccessful()) {
 
 698                   numFailedLinkResolve.incrementAndGet();
 
 701                 if (opResult.isFromCache()) {
 
 702                   numSuccessfulLinkResolveFromCache.incrementAndGet();
 
 704                   numSuccessfulLinkResolveFromFromServer.incrementAndGet();
 
 708                 nodeTxn.getProcessingNode().setOpResult(opResult);
 
 709                 nodeTxn.getProcessingNode().changeState(
 
 710                     NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
 
 711                     NodeProcessingAction.SELF_LINK_RESOLVE_OK);
 
 713                 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
 
 714                 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
 
 717                 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Self Link retrieval for link,"
 
 718                     + txn.getSelfLinkWithModifiers() + ", failed with error code,"
 
 719                     + nodeTxn.getOpResult().getResultCode() + ", and message,"
 
 720                     + nodeTxn.getOpResult().getResult());
 
 722                 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
 
 723                 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
 
 725                 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
 
 726                     NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
 
 728                 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
 
 733             aaiWorkOnHand.decrementAndGet();
 
 745    * @param nodeId the node id
 
 747   private void processNeighbors(String nodeId) {
 
 749     if (nodeId == null) {
 
 750       LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
 
 751           + " neighbors because nodeId is null.");
 
 755     ActiveInventoryNode ain = nodeCache.get(nodeId);
 
 758       LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
 
 759           + " neighbors because node could not be found in nodeCache with id, " + nodeId);
 
 764      * process complex attribute and relationships
 
 767     boolean neighborsProcessedSuccessfully = true;
 
 769     for (JsonNode n : ain.getComplexGroups()) {
 
 770       neighborsProcessedSuccessfully &= decodeComplexAttributeGroup(ain, n);
 
 773     for (RelationshipList relationshipList : ain.getRelationshipLists()) {
 
 774       neighborsProcessedSuccessfully &= addSelfLinkRelationshipChildren(ain, relationshipList);
 
 778     if (neighborsProcessedSuccessfully) {
 
 779       ain.changeState(NodeProcessingState.READY, NodeProcessingAction.NEIGHBORS_PROCESSED_OK);
 
 781       ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
 
 786      * If neighbors fail to process, there is already a call to change the state within the
 
 787      * relationship and neighbor processing functions.
 
 793    * Find and mark root node.
 
 795    * @param queryParams the query params
 
 796    * @return true, if successful
 
 798   private void findAndMarkRootNode(QueryParams queryParams) {
 
 800     if (isRootNodeFound()) {
 
 804     for (ActiveInventoryNode cacheNode : nodeCache.values()) {
 
 806       if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
 
 807         cacheNode.setNodeDepth(0);
 
 808         cacheNode.setRootNode(true);
 
 809         LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
 
 810         setRootNodeFound(true);
 
 817    * Process current node states.
 
 820   private void processCurrentNodeStates(QueryParams queryParams) {
 
 822      * Force an evaluation of node depths before determining if we should limit state-based
 
 823      * traversal or processing.
 
 826     findAndMarkRootNode(queryParams);
 
 828     verifyOutboundNeighbors();
 
 830     for (ActiveInventoryNode cacheNode : nodeCache.values()) {
 
 832       if (LOG.isDebugEnabled()) {
 
 833         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
 834             "processCurrentNodeState(), nid = "
 
 835             + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
 
 838       switch (cacheNode.getState()) {
 
 841           processInitialState(cacheNode.getNodeId());
 
 850         case SELF_LINK_UNRESOLVED: {
 
 851           performSelfLinkResolve(cacheNode.getNodeId());
 
 855         case SELF_LINK_RESPONSE_UNPROCESSED: {
 
 856           processSelfLinkResponse(cacheNode.getNodeId());
 
 860         case NEIGHBORS_UNPROCESSED: {
 
 863            * We use the rootNodeDiscovered flag to ignore depth retrieval thresholds until the root
 
 864            * node is identified. Then the evaluative depth calculations should re-balance the graph
 
 865            * around the root node.
 
 868           if (!isRootNodeFound() || cacheNode.getNodeDepth() < this.visualizationConfigs
 
 869               .getMaxSelfLinkTraversalDepth()) {
 
 871             if (LOG.isDebugEnabled()) {
 
 872               LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
 873                   "processCurrentNodeState() -- Node at max depth,"
 
 874                   + " halting processing at current state = -- "
 
 875                       + cacheNode.getState() + " nodeId = " + cacheNode.getNodeId());
 
 878             processNeighbors(cacheNode.getNodeId());
 
 893    * Adds the complex group to node.
 
 895    * @param targetNode the target node
 
 896    * @param attributeGroup the attribute group
 
 897    * @return true, if successful
 
 899   private boolean addComplexGroupToNode(ActiveInventoryNode targetNode, JsonNode attributeGroup) {
 
 901     if (attributeGroup == null) {
 
 902       targetNode.changeState(NodeProcessingState.ERROR,
 
 903           NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
 
 907     RelationshipList relationshipList = null;
 
 909     if (attributeGroup.isObject()) {
 
 911       Iterator<Entry<String, JsonNode>> fields = attributeGroup.fields();
 
 912       Entry<String, JsonNode> field = null;
 
 916       while (fields.hasNext()) {
 
 917         field = fields.next();
 
 918         fieldName = field.getKey();
 
 919         fieldValue = field.getValue();
 
 921         if (fieldValue.isObject()) {
 
 923           if (fieldName.equals("relationship-list")) {
 
 927                   mapper.readValue(field.getValue().toString(), RelationshipList.class);
 
 929               if (relationshipList != null) {
 
 930                 targetNode.addRelationshipList(relationshipList);
 
 933             } catch (Exception exc) {
 
 934               LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse"
 
 935                   + " relationship-list attribute. Parse resulted in error, "
 
 936                   + exc.getLocalizedMessage());
 
 937               targetNode.changeState(NodeProcessingState.ERROR,
 
 938                   NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_ERROR);
 
 943             targetNode.addComplexGroup(fieldValue);
 
 946         } else if (fieldValue.isArray()) {
 
 947           if (LOG.isDebugEnabled()) {
 
 948             LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
 949                 "Unexpected array type with a key = " + fieldName);
 
 951         } else if (fieldValue.isValueNode()) {
 
 952           if (oxmEntityLookup.getEntityDescriptors().get(field.getKey()) == null) {
 
 954              * property key is not an entity type, add it to our property set.
 
 956             targetNode.addProperty(field.getKey(), fieldValue.asText());
 
 962     } else if (attributeGroup.isArray()) {
 
 963       if (LOG.isDebugEnabled()) {
 
 964         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
 965             "Unexpected array type for attributeGroup = " + attributeGroup);
 
 967     } else if (attributeGroup.isValueNode()) {
 
 968       if (LOG.isDebugEnabled()) {
 
 969         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
 970             "Unexpected value type for attributeGroup = " + attributeGroup);
 
 977   public int getNumSuccessfulLinkResolveFromCache() {
 
 978     return numSuccessfulLinkResolveFromCache.get();
 
 981   public int getNumSuccessfulLinkResolveFromFromServer() {
 
 982     return numSuccessfulLinkResolveFromFromServer.get();
 
 985   public int getNumFailedLinkResolve() {
 
 986     return numFailedLinkResolve.get();
 
 989   public InlineMessage getInlineMessage() {
 
 990     return inlineMessage;
 
 993   public void setInlineMessage(InlineMessage inlineMessage) {
 
 994     this.inlineMessage = inlineMessage;
 
 997   public void setMaxSelfLinkTraversalDepth(int depth) {
 
 998     this.maxSelfLinkTraversalDepth = depth;
 
1001   public int getMaxSelfLinkTraversalDepth() {
 
1002     return this.maxSelfLinkTraversalDepth;
 
1005   public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
 
1010    * Gets the relationship primary key values.
 
1013    * @param entityType the entity type
 
1014    * @param pkeyNames the pkey names
 
1015    * @return the relationship primary key values
 
1017   private String getRelationshipPrimaryKeyValues(Relationship r, String entityType,
 
1018       List<String> pkeyNames) {
 
1020     StringBuilder sb = new StringBuilder(64);
 
1022     if (pkeyNames.size() > 0) {
 
1023       String primaryKey = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(0));
 
1024       if (primaryKey != null) {
 
1026         sb.append(primaryKey);
 
1029         // this should be a fatal error because unless we can
 
1030         // successfully retrieve all the expected keys we'll end up
 
1031         // with a garbage node
 
1032         LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract"
 
1033             + " keyName, " + entityType + "." + pkeyNames.get(0)
 
1034             + ", from relationship data, " + r.toString());
 
1038       for (int i = 1; i < pkeyNames.size(); i++) {
 
1040         String kv = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(i));
 
1042           sb.append("/").append(kv);
 
1044           // this should be a fatal error because unless we can
 
1045           // successfully retrieve all the expected keys we'll end up
 
1046           // with a garbage node
 
1047           LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR:  failed to extract keyName, "
 
1048               + entityType + "." + pkeyNames.get(i)
 
1049               + ", from relationship data, " + r.toString());
 
1054       return sb.toString();
 
1063    * Extract key value from relation data.
 
1066    * @param keyName the key name
 
1067    * @return the string
 
1069   private String extractKeyValueFromRelationData(Relationship r, String keyName) {
 
1071     RelationshipData[] rdList = r.getRelationshipData();
 
1073     for (RelationshipData relData : rdList) {
 
1075       if (relData.getRelationshipKey().equals(keyName)) {
 
1076         return relData.getRelationshipValue();
 
1084    * Determine node id and key.
 
1086    * @param ain the ain
 
1087    * @return true, if successful
 
1089   private boolean addNodeQueryParams(ActiveInventoryNode ain) {
 
1092       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "ActiveInventoryNode is null");
 
1096     List<String> pkeyNames =
 
1097         oxmEntityLookup.getEntityDescriptors().get(ain.getEntityType()).getPrimaryKeyAttributeNames();
 
1099     if (pkeyNames == null || pkeyNames.size() == 0) {
 
1100       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Primary key names is empty");
 
1104     StringBuilder sb = new StringBuilder(64);
 
1106     if (pkeyNames.size() > 0) {
 
1107       String primaryKey = ain.getProperties().get(pkeyNames.get(0));
 
1108       if (primaryKey != null) {
 
1109         sb.append(primaryKey);
 
1111         // this should be a fatal error because unless we can
 
1112         // successfully retrieve all the expected keys we'll end up
 
1113         // with a garbage node
 
1114         LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
 
1115             + pkeyNames.get(0) + ", from entity properties");
 
1119       for (int i = 1; i < pkeyNames.size(); i++) {
 
1121         String kv = ain.getProperties().get(pkeyNames.get(i));
 
1123           sb.append("/").append(kv);
 
1125           // this should be a fatal error because unless we can
 
1126           // successfully retrieve all the expected keys we'll end up
 
1127           // with a garbage node
 
1128           LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
 
1129               + pkeyNames.get(i) + ", from entity properties");
 
1134       /*final String nodeId = NodeUtils.generateUniqueShaDigest(ain.getEntityType(),
 
1135           NodeUtils.concatArray(pkeyNames, "/"), sb.toString());*/
 
1137       //ain.setNodeId(nodeId);
 
1138       ain.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
 
1139       ain.setPrimaryKeyValue(sb.toString());
 
1141       if (ain.getEntityType() != null && ain.getPrimaryKeyName() != null
 
1142           && ain.getPrimaryKeyValue() != null) {
 
1144             ain.getEntityType() + "." + ain.getPrimaryKeyName() + ":" + ain.getPrimaryKeyValue());
 
1155    * Adds the self link relationship children.
 
1157    * @param processingNode the processing node
 
1158    * @param relationshipList the relationship list
 
1159    * @return true, if successful
 
1161   private boolean addSelfLinkRelationshipChildren(ActiveInventoryNode processingNode,
 
1162       RelationshipList relationshipList) {
 
1164     if (relationshipList == null) {
 
1165       LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "No relationships added to parent node = "
 
1166           + processingNode.getNodeId() + " because relationshipList is empty");
 
1167       processingNode.changeState(NodeProcessingState.ERROR,
 
1168           NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
 
1172     Relationship[] relationshipArray = relationshipList.getRelationshipList();
 
1173     OxmEntityDescriptor descriptor = null;
 
1174     String repairedSelfLink = null;
 
1176     if (relationshipArray != null) {
 
1178       ActiveInventoryNode newNode = null;
 
1179       String resourcePath = null;
 
1181       for (Relationship r : relationshipArray) {
 
1183         resourcePath = ActiveInventoryAdapter.extractResourcePath(r.getRelatedLink());
 
1185         String nodeId = NodeUtils.generateUniqueShaDigest(resourcePath);
 
1187         if (nodeId == null) {
 
1189           LOG.error(AaiUiMsgs.SKIPPING_RELATIONSHIP, r.toString());
 
1190           processingNode.changeState(NodeProcessingState.ERROR,
 
1191               NodeProcessingAction.NODE_IDENTITY_ERROR);
 
1195         newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
 
1197         String entityType = r.getRelatedTo();
 
1199         if (r.getRelationshipData() != null) {
 
1200           for (RelationshipData rd : r.getRelationshipData()) {
 
1201             newNode.addQueryParam(rd.getRelationshipKey() + ":" + rd.getRelationshipValue());
 
1205         descriptor = oxmEntityLookup.getEntityDescriptors().get(r.getRelatedTo());
 
1207         newNode.setNodeId(nodeId);
 
1208         newNode.setEntityType(entityType);
 
1209         newNode.setSelfLink(resourcePath);
 
1211         processingNode.addOutboundNeighbor(nodeId);
 
1213         if (descriptor != null) {
 
1215           List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
 
1217           newNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
 
1218               NodeProcessingAction.SELF_LINK_SET);
 
1220           newNode.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
 
1222           String primaryKeyValues = getRelationshipPrimaryKeyValues(r, entityType, pkeyNames);
 
1223           newNode.setPrimaryKeyValue(primaryKeyValues);
 
1227           LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR,
 
1228               "Failed to parse entity because OXM descriptor could not be found for type = "
 
1229                   + r.getRelatedTo());
 
1231           newNode.changeState(NodeProcessingState.ERROR,
 
1232               NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
 
1236         if (nodeCache.putIfAbsent(nodeId, newNode) != null) {
 
1237           if (LOG.isDebugEnabled()) {
 
1238             LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
 
1239                 "Failed to add node to nodeCache because it already exists.  Node id = "
 
1240                     + newNode.getNodeId());
 
1253    * Process initial state.
 
1255    * @param nodeId the node id
 
1257   private void processInitialState(String nodeId) {
 
1259     if (nodeId == null) {
 
1260       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
 
1264     ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
 
1266     if (cachedNode == null) {
 
1267       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node cannot be"
 
1268           + " found for nodeId, " + nodeId);
 
1272     if (cachedNode.getSelfLink() == null) {
 
1274       if (cachedNode.getNodeId() == null ) {
 
1277          * if the self link is null at the INIT state, which could be valid if this node is a
 
1278          * complex attribute group which didn't originate from a self-link, but in that situation
 
1279          * both the node id and node key should already be set.
 
1282         cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
 
1286       if (cachedNode.getNodeId() != null) {
 
1289          * This should be the success path branch if the self-link is not set
 
1292         cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
 
1293             NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
 
1299       if (cachedNode.hasResolvedSelfLink()) {
 
1300         LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
 
1301         cachedNode.changeState(NodeProcessingState.ERROR,
 
1302             NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
 
1304         cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
 
1305             NodeProcessingAction.SELF_LINK_SET);
 
1311    * Process skeleton node.
 
1313    * @param skeletonNode the skeleton node
 
1314    * @param queryParams the query params
 
1316   private void processSearchableEntity(SearchableEntity searchTargetEntity, QueryParams queryParams) {
 
1318     if (searchTargetEntity == null) {
 
1322     if (searchTargetEntity.getId() == null) {
 
1323       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
 
1324           + " node because nodeId is null for node, " + searchTargetEntity.getLink());
 
1328     ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
 
1330     newNode.setNodeId(searchTargetEntity.getId());
 
1331     newNode.setEntityType(searchTargetEntity.getEntityType());
 
1332     newNode.setPrimaryKeyName(getEntityTypePrimaryKeyName(searchTargetEntity.getEntityType()));
 
1333     newNode.setPrimaryKeyValue(searchTargetEntity.getEntityPrimaryKeyValue());
 
1335     if (newNode.getEntityType() != null && newNode.getPrimaryKeyName() != null
 
1336         && newNode.getPrimaryKeyValue() != null) {
 
1337       newNode.addQueryParam(
 
1338           newNode.getEntityType() + "." + newNode.getPrimaryKeyName() + ":" + newNode.getPrimaryKeyValue());
 
1341      * This code may need some explanation. In any graph there will be a single root node. The root
 
1342      * node is really the center of the universe, and for now, we are tagging the search target as
 
1343      * the root node. Everything else in the visualization of the graph will be centered around this
 
1344      * node as the focal point of interest.
 
1346      * Due to it's special nature, there will only ever be one root node, and it's node depth will
 
1347      * always be equal to zero.
 
1350     if (!isRootNodeFound()) {
 
1351       if (queryParams.getSearchTargetNodeId().equals(newNode.getNodeId())) {
 
1352         newNode.setNodeDepth(0);
 
1353         newNode.setRootNode(true);
 
1354         LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
 
1355         setRootNodeFound(true);
 
1359     newNode.setSelfLink(searchTargetEntity.getLink());
 
1361     nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
 
1364   private int getTotalWorkOnHand() {
 
1366     int numNodesWithPendingStates = 0;
 
1368     if( isRootNodeFound()) {
 
1369       evaluateNodeDepths();
 
1372     for (ActiveInventoryNode n : nodeCache.values()) {
 
1374       switch (n.getState()) {
 
1378           // do nothing, these are our normal
 
1383         case NEIGHBORS_UNPROCESSED: {
 
1385           if (n.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
 
1387              * Only process our neighbors relationships if our current depth is less than the max
 
1390             numNodesWithPendingStates++;
 
1399            * for all other states, there is work to be done
 
1401           numNodesWithPendingStates++;
 
1408     LOG.debug(AaiUiMsgs.OUTSTANDING_WORK_PENDING_NODES,
 
1409         String.valueOf(numNodesWithPendingStates));
 
1411     int totalWorkOnHand = aaiWorkOnHand.get() + numNodesWithPendingStates;
 
1413     return totalWorkOnHand;
 
1418    * Checks for out standing work.
 
1420    * @return true, if successful
 
1422   private void processOutstandingWork(QueryParams queryParams) {
 
1424     while (getTotalWorkOnHand() > 0) {
 
1427        * Force an evaluation of node depths before determining if we should limit state-based
 
1428        * traversal or processing.
 
1431       processCurrentNodeStates(queryParams);
 
1435       } catch (InterruptedException exc) {
 
1436         LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
 
1437         Thread.currentThread().interrupt();
 
1446    * @see org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai.sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams)
 
1449   public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
 
1454       if (searchtargetEntity == null) {
 
1455         LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, contextIdStr + " - Failed to"
 
1456             + " processSelfLinks, searchtargetEntity is null");
 
1460       long startTimeInMs = System.currentTimeMillis();
 
1462       processSearchableEntity(searchtargetEntity, queryParams);
 
1465        * This method is blocking until we decouple it with a CountDownLatch await condition,
 
1466        * and make the internal graph processing more event-y.
 
1469       processOutstandingWork(queryParams);
 
1471       long totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
 
1473       long opTime = System.currentTimeMillis() - startTimeInMs;
 
1475       LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
 
1476           String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
 
1478     } catch (Exception exc) {
 
1479       LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
 
1485    * Verify outbound neighbors.
 
1487   private void verifyOutboundNeighbors() {
 
1489     for (ActiveInventoryNode srcNode : nodeCache.values()) {
 
1491       for (String targetNodeId : srcNode.getOutboundNeighbors()) {
 
1493         ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
 
1495         if (targetNode != null && srcNode.getNodeId() != null) {
 
1497           targetNode.addInboundNeighbor(srcNode.getNodeId());
 
1499           if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
 
1500             targetNode.addOutboundNeighbor(srcNode.getNodeId());
 
1512    * Evaluate node depths.
 
1514   private void evaluateNodeDepths() {
 
1516     int numChanged = -1;
 
1517     int numAttempts = 0;
 
1519     while (numChanged != 0) {
 
1524       for (ActiveInventoryNode srcNode : nodeCache.values()) {
 
1526         if (srcNode.getState() == NodeProcessingState.INIT) {
 
1529            * this maybe the only state that we don't want to to process the node depth on, because
 
1530            * typically it won't have any valid fields set, and it may remain in a partial state
 
1531            * until we have processed the self-link.
 
1538         for (String targetNodeId : srcNode.getOutboundNeighbors()) {
 
1539           ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
 
1541           if (targetNode != null) {
 
1543             if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
 
1549         for (String targetNodeId : srcNode.getInboundNeighbors()) {
 
1550           ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
 
1552           if (targetNode != null) {
 
1554             if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
 
1561       if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
 
1562         LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
 
1568     if (LOG.isDebugEnabled()) {
 
1569       if (numAttempts > 0) {
 
1570         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
1571             "Evaluate node depths completed in " + numAttempts + " attempts");
 
1573         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, 
 
1574             "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
 
1582    * Gets the entity type primary key name.
 
1584    * @param entityType the entity type
 
1585    * @return the entity type primary key name
 
1589   private String getEntityTypePrimaryKeyName(String entityType) {
 
1591     if (entityType == null) {
 
1592       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary key"
 
1593           + " name because entity type is null");
 
1597     OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
 
1599     if (descriptor == null) {
 
1600       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "oxm entity"
 
1601           + " descriptor for entityType = " + entityType);
 
1605     List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
 
1607     if (pkeyNames == null || pkeyNames.size() == 0) {
 
1608       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary"
 
1609           + " key because descriptor primary key names is empty");
 
1613     return NodeUtils.concatArray(pkeyNames, "/");