8963ff73a315d6ca21aaca24d00b8a22285c17d9
[aai/sparky-be.git] / src / main / java / org / onap / aai / sparky / viewandinspect / services / BaseGizmoVisualizationContext.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 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
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.onap.aai.sparky.viewandinspect.services;
24
25 import static java.util.concurrent.CompletableFuture.supplyAsync;
26
27 import java.io.IOException;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ExecutorService;
32 import java.util.concurrent.atomic.AtomicInteger;
33
34 import org.onap.aai.cl.api.Logger;
35 import org.onap.aai.cl.eelf.LoggerFactory;
36 import org.onap.aai.restclient.client.OperationResult;
37 import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
38 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
39 import org.onap.aai.sparky.dal.GizmoAdapter;
40 import org.onap.aai.sparky.logging.AaiUiMsgs;
41 import org.onap.aai.sparky.sync.entity.SearchableEntity;
42 import org.onap.aai.sparky.util.NodeUtils;
43 import org.onap.aai.sparky.viewandinspect.config.SparkyConstants;
44 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
45 import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
46 import org.onap.aai.sparky.viewandinspect.entity.GizmoEntity;
47 import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipEntity;
48 import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipHint;
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.enumeration.NodeProcessingAction;
53 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState;
54 import org.onap.aai.sparky.viewandinspect.task.PerformGizmoNodeSelfLinkProcessingTask;
55
56 import com.fasterxml.jackson.annotation.JsonInclude.Include;
57 import com.fasterxml.jackson.databind.ObjectMapper;
58 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
59
60 /**
61  * The Class SelfLinkNodeCollector.
62  */
63 public class BaseGizmoVisualizationContext implements VisualizationContext {
64
65   private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100;
66
67   private static final Logger LOG =
68       LoggerFactory.getInstance().getLogger(BaseGizmoVisualizationContext.class);
69
70   private final GizmoAdapter gizmoAdapter;
71
72   private AtomicInteger numLinksDiscovered;
73   private AtomicInteger numSuccessfulLinkResolveFromCache;
74   private AtomicInteger numSuccessfulLinkResolveFromFromServer;
75   private AtomicInteger numFailedLinkResolve;
76   private AtomicInteger aaiWorkOnHand;
77
78   private VisualizationConfigs visualizationConfigs;
79
80   private AtomicInteger totalLinksRetrieved;
81
82   private final long contextId;
83   private final String contextIdStr;
84   private long lastProcessStatesSummaryLogInMs = -1;
85
86
87   private ObjectMapper mapper;
88   private InlineMessage inlineMessage = null;
89
90   private ExecutorService graphExecutorService;
91   private OxmEntityLookup oxmEntityLookup;
92   private boolean rootNodeFound;
93
94   /*
95    * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
96    * re-requesting the same self-links over-and-over again, to speed up the overall render time and
97    * more importantly to reduce the network cost of determining information we already have.
98    */
99   private ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
100
101   /**
102    * Instantiates a new self link node collector.
103    *
104    * @param loader the loader
105    * @throws Exception the exception
106    */
107   public BaseGizmoVisualizationContext(long contextId, GizmoAdapter gizmoAdapter,
108       ExecutorService graphExecutorService, VisualizationConfigs visualizationConfigs,
109       OxmEntityLookup oxmEntityLookup) throws Exception {
110
111     this.contextId = contextId;
112     this.contextIdStr = "[Context-Id=" + contextId + "]";
113     this.gizmoAdapter = gizmoAdapter;
114     this.graphExecutorService = graphExecutorService;
115     this.visualizationConfigs = visualizationConfigs;
116     this.oxmEntityLookup = oxmEntityLookup;
117
118     this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
119     this.numLinksDiscovered = new AtomicInteger(0);
120     this.totalLinksRetrieved = new AtomicInteger(0);
121     this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
122     this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
123     this.numFailedLinkResolve = new AtomicInteger(0);
124     this.aaiWorkOnHand = new AtomicInteger(0);
125
126     this.mapper = new ObjectMapper();
127     mapper.setSerializationInclusion(Include.NON_EMPTY);
128     mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
129     this.rootNodeFound = false;
130   }
131
132   protected boolean isRootNodeFound() {
133     return rootNodeFound;
134   }
135
136   protected void setRootNodeFound(boolean rootNodeFound) {
137     this.rootNodeFound = rootNodeFound;
138   }
139
140   public long getContextId() {
141     return contextId;
142   }
143
144   public GizmoAdapter getGizmoAdapter() {
145     return gizmoAdapter;
146   }
147
148   /**
149    * Process self link response.
150    *
151    * @param nodeId the node id
152    */
153   private void processSelfLinkResponse(String nodeId) {
154
155     if (nodeId == null) {
156       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
157           "Cannot process self link" + " response because nodeId is null");
158       return;
159     }
160
161     ActiveInventoryNode ain = nodeCache.get(nodeId);
162
163     if (ain == null) {
164       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
165           "Cannot process self link response" + " because can't find node for id = " + nodeId);
166       return;
167     }
168
169     GizmoEntity gizmoEntity = null;
170
171     try {
172       gizmoEntity = mapper.readValue(ain.getOpResult().getResult(), GizmoEntity.class);
173     } catch (Exception exc) {
174       exc.printStackTrace();
175       LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
176           + " response str into JsonNode with error, " + exc.getLocalizedMessage());
177       ain.changeState(NodeProcessingState.ERROR,
178           NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
179       return;
180     }
181
182     if (gizmoEntity == null) {
183
184       LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR,
185           "Failed to parse json node str." + " Parse resulted a null value.");
186       ain.changeState(NodeProcessingState.ERROR,
187           NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
188       return;
189     }
190
191     /*
192      * Now that we have the gizmo entity we can populate the AIN node with it, as well as the
193      * relationships
194      */
195
196     ain.setEntityType(gizmoEntity.getType());
197
198     ain.setPrimaryKeyName(getEntityTypePrimaryKeyName(gizmoEntity.getType()));
199     
200     OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(gizmoEntity);
201
202     if (descriptor != null) {
203       ain.setPrimaryKeyValue(getPrimaryKeyValues(gizmoEntity.getProperties(),
204           descriptor.getPrimaryKeyAttributeNames()));
205     } else {
206       LOG.error(AaiUiMsgs.ERROR_GENERIC, "Could not determine oxm descriptor for entity type = " + gizmoEntity.getType());
207     }
208     
209     gizmoEntity.getProperties().forEach((key, value) -> {
210       ain.getProperties().put(key, value);
211     });
212
213     // add edit attributes link
214     if (ain.getSelfLink() != null) {
215       ain.addProperty(SparkyConstants.URI_ATTR_NAME, ain.getSelfLink());
216     }
217     
218
219
220     /*
221      * Only discover neighbors if our depth is less than the Max-Traversal-Depth
222      */
223
224     if (ain.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
225
226       /*
227        * I think the next thing to do is:
228        * 
229        * 1. Calculate the source / target node id 2. Add the nodeId to the incoming / outgoing links
230        * collection 3. Add the node to the node cache for processing
231        */
232
233       String resourceLink = null;
234       String relationshipNodeId = null;
235       ActiveInventoryNode relationshipNode = null;
236
237       for (GizmoRelationshipHint inRelationship : gizmoEntity.getIn()) {
238
239         if (inRelationship.getSource() != null) {
240
241           resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(inRelationship.getSource());
242           relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink);
243
244           if (!nodeCache.containsKey(relationshipNodeId)) {
245
246             relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup);
247             relationshipNode.setNodeId(relationshipNodeId);
248             relationshipNode.setSelfLink(resourceLink);
249             relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
250                 NodeProcessingAction.NEW_NODE_PROCESSED);
251
252             ain.addInboundNeighbor(relationshipNodeId);
253
254             addNode(relationshipNode);
255
256           }
257         }
258
259       }
260
261       for (GizmoRelationshipHint outRelationship : gizmoEntity.getOut()) {
262
263         if (outRelationship.getTarget() != null) {
264
265           resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(outRelationship.getTarget());
266           relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink);
267
268           if (!nodeCache.containsKey(relationshipNodeId)) {
269
270             relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup);
271             relationshipNode.setNodeId(relationshipNodeId);
272             relationshipNode.setSelfLink(resourceLink);
273             relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
274                 NodeProcessingAction.NEW_NODE_PROCESSED);
275
276             ain.addOutboundNeighbor(relationshipNodeId);
277
278             addNode(relationshipNode);
279
280           }
281         }
282
283       }
284     }
285
286     ain.changeState(NodeProcessingState.READY, NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
287
288   }
289
290   /**
291    * Perform self link resolve.
292    *
293    * @param nodeId the node id
294    */
295   private void performSelfLinkResolve(String nodeId) {
296
297     if (nodeId == null) {
298       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
299           "Resolve of self-link" + " has been skipped because provided nodeId is null");
300       return;
301     }
302
303     ActiveInventoryNode ain = nodeCache.get(nodeId);
304
305     if (ain == null) {
306       LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId
307           + ", from node cache. Resolve self-link method has been skipped.");
308       return;
309     }
310
311     if (!ain.isSelfLinkPendingResolve()) {
312
313       ain.setSelfLinkPendingResolve(true);
314
315       // kick off async self-link resolution
316
317       if (LOG.isDebugEnabled()) {
318         LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
319             "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink());
320       }
321
322       numLinksDiscovered.incrementAndGet();
323
324       /*
325        * If the current node is the search target, we want to see everything the node has to offer
326        * from the self-link and not filter it to a single node.
327        */
328
329       NodeProcessingTransaction txn = new NodeProcessingTransaction();
330       txn.setProcessingNode(ain);
331       txn.setRequestParameters(null);
332       aaiWorkOnHand.incrementAndGet();
333       supplyAsync(new PerformGizmoNodeSelfLinkProcessingTask(txn, null, gizmoAdapter),
334           graphExecutorService).whenComplete((nodeTxn, error) -> {
335
336             if (error != null) {
337
338               /*
339                * an error processing the self link should probably result in the node processing
340                * state shifting to ERROR
341                */
342
343               nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
344
345               nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
346                   NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
347
348               nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
349
350             } else {
351
352               totalLinksRetrieved.incrementAndGet();
353
354               OperationResult opResult = nodeTxn.getOpResult();
355
356               if (opResult != null && opResult.wasSuccessful()) {
357
358                 if (!opResult.wasSuccessful()) {
359                   numFailedLinkResolve.incrementAndGet();
360                 }
361
362                 if (opResult.isFromCache()) {
363                   numSuccessfulLinkResolveFromCache.incrementAndGet();
364                 } else {
365                   numSuccessfulLinkResolveFromFromServer.incrementAndGet();
366                 }
367
368                 // success path
369                 nodeTxn.getProcessingNode().setOpResult(opResult);
370                 nodeTxn.getProcessingNode().changeState(
371                     NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
372                     NodeProcessingAction.SELF_LINK_RESOLVE_OK);
373
374                 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
375                 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
376
377               } else {
378                 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
379                     "Self Link retrieval for link," + txn.getSelfLinkWithModifiers()
380                         + ", failed with error code," + nodeTxn.getOpResult().getResultCode()
381                         + ", and message," + nodeTxn.getOpResult().getResult());
382
383                 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
384                 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
385
386                 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
387                     NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
388
389                 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
390
391               }
392             }
393
394             aaiWorkOnHand.decrementAndGet();
395
396           });
397
398     }
399
400   }
401
402   public GizmoRelationshipEntity getGizmoRelationshipEntity(String gizmoJsonResponse) {
403
404     GizmoRelationshipEntity gizmoRelationship = null;
405     try {
406       gizmoRelationship = mapper.readValue(gizmoJsonResponse, GizmoRelationshipEntity.class);
407     } catch (IOException exc) {
408       LOG.error(AaiUiMsgs.ERROR_GENERIC, "Failed to map json to GizmoRelationshipEntity.  Error: " + exc.getMessage());
409     }
410
411     return gizmoRelationship;
412
413   }
414
415   public String getPrimaryKeyValues(Map<String, String> properties, List<String> pkeyNames) {
416
417     StringBuilder sb = new StringBuilder(64);
418
419     if (pkeyNames.size() > 0) {
420       String primaryKey = properties.get(pkeyNames.get(0));
421       if (primaryKey != null) {
422         sb.append(primaryKey);
423       } else {
424         // this should be a fatal error because unless we can
425         // successfully retrieve all the expected keys we'll end up
426         // with a garbage node
427         LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract" + " keyName, "
428             + pkeyNames.get(0) + ", from properties , " + properties);
429         return null;
430       }
431
432       for (int i = 1; i < pkeyNames.size(); i++) {
433
434         String kv = properties.get(pkeyNames.get(i));
435         if (kv != null) {
436           sb.append("/").append(kv);
437         } else {
438           // this should be a fatal error because unless we can
439           // successfully retrieve all the expected keys we'll end up
440           // with a garbage node
441           LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR:  failed to extract keyName, "
442               + pkeyNames.get(i) + ", from properties, " + properties);
443           return null;
444         }
445       }
446
447       return sb.toString();
448
449     }
450
451     return null;
452
453   }
454
455
456
457   /**
458    * Find and mark root node.
459    *
460    * @param queryParams the query params
461    * @return true, if successful
462    */
463   private void findAndMarkRootNode(QueryParams queryParams) {
464
465     if (isRootNodeFound()) {
466       return;
467     }
468
469     for (ActiveInventoryNode cacheNode : nodeCache.values()) {
470
471       if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
472         cacheNode.setNodeDepth(0);
473         cacheNode.setRootNode(true);
474         LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
475         setRootNodeFound(true);
476       }
477     }
478
479   }
480
481   public void addNode(ActiveInventoryNode node) {
482
483     if (node == null) {
484       return;
485     }
486
487     nodeCache.putIfAbsent(node.getNodeId(), node);
488   }
489
490   public VisualizationConfigs getVisualizationConfigs() {
491     return visualizationConfigs;
492   }
493
494   public void setVisualizationConfigs(VisualizationConfigs visualizationConfigs) {
495     this.visualizationConfigs = visualizationConfigs;
496   }
497
498   public OxmEntityLookup getOxmEntityLookup() {
499     return oxmEntityLookup;
500   }
501
502   public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) {
503     this.oxmEntityLookup = oxmEntityLookup;
504   }
505
506   public ObjectMapper getMapper() {
507     return mapper;
508   }
509
510   public void setMapper(ObjectMapper mapper) {
511     this.mapper = mapper;
512   }
513
514   private void dumpThrottledWorkOnHandLog() {
515     dumpThrottledWorkOnHandLog(false);
516   }
517
518   private void dumpThrottledWorkOnHandLog(boolean override) {
519
520     if ((lastProcessStatesSummaryLogInMs < 0)
521         || ((System.currentTimeMillis() > (lastProcessStatesSummaryLogInMs + 5000))) || override) {
522
523       lastProcessStatesSummaryLogInMs = System.currentTimeMillis();
524
525       int numInit = 0;
526       int numReady = 0;
527       int numError = 0;
528       int numSelfLinkUnresolved = 0;
529       int numSelfLinkResponseUnprocessed = 0;
530
531       for (ActiveInventoryNode cacheNode : nodeCache.values()) {
532
533         switch (cacheNode.getState()) {
534
535           case INIT: {
536             numInit++;
537             break;
538           }
539
540           case READY: {
541             numReady++;
542             break;
543           }
544           case ERROR: {
545             numError++;
546             break;
547           }
548
549           case SELF_LINK_UNRESOLVED: {
550             numSelfLinkUnresolved++;
551             break;
552           }
553
554           case SELF_LINK_RESPONSE_UNPROCESSED: {
555             numSelfLinkResponseUnprocessed++;
556             break;
557           }
558
559           default:
560             break;
561         }
562
563       }
564
565       LOG.info(AaiUiMsgs.INFO_GENERIC,
566           String.format(
567               "ProcessCurrentStates for ContextId=%s, [PendingTxns=%d, numInit=%d, numSelfLinkUnresolved=%d, numSelfLinkResponseUnProcessed=%d, numReady=%d, numError=%d]",
568               contextIdStr, aaiWorkOnHand.get(), numInit, numSelfLinkUnresolved, numSelfLinkResponseUnprocessed,
569               numReady, numError));
570     }
571
572   }
573
574   /**
575    * Process current node states.
576    *
577    * @param rootNodeDiscovered the root node discovered
578    */
579   private void processCurrentNodeStates(QueryParams queryParams) {
580     /*
581      * Force an evaluation of node depths before determining if we should limit state-based
582      * traversal or processing.
583      */
584
585     findAndMarkRootNode(queryParams);
586
587     verifyOutboundNeighbors();
588
589     for (ActiveInventoryNode cacheNode : nodeCache.values()) {
590
591       if (LOG.isDebugEnabled()) {
592         LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "processCurrentNodeState(), nid = "
593             + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
594       }
595
596       switch (cacheNode.getState()) {
597
598         case INIT: {
599           processInitialState(cacheNode.getNodeId());
600           break;
601         }
602
603         case READY: 
604         case ERROR: {
605           break;
606         }
607
608         case SELF_LINK_UNRESOLVED: {
609           performSelfLinkResolve(cacheNode.getNodeId());
610           break;
611         }
612
613         case SELF_LINK_RESPONSE_UNPROCESSED: {
614           processSelfLinkResponse(cacheNode.getNodeId());
615           break;
616         }
617
618         default:
619           break;
620       }
621
622     }
623     
624     dumpThrottledWorkOnHandLog();
625
626   }
627
628
629
630   public int getNumSuccessfulLinkResolveFromCache() {
631     return numSuccessfulLinkResolveFromCache.get();
632   }
633
634   public int getNumSuccessfulLinkResolveFromFromServer() {
635     return numSuccessfulLinkResolveFromFromServer.get();
636   }
637
638   public int getNumFailedLinkResolve() {
639     return numFailedLinkResolve.get();
640   }
641
642   public InlineMessage getInlineMessage() {
643     return inlineMessage;
644   }
645
646   public void setInlineMessage(InlineMessage inlineMessage) {
647     this.inlineMessage = inlineMessage;
648   }
649
650   public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
651     return nodeCache;
652   }
653
654
655
656   /**
657    * Process initial state.
658    *
659    * @param nodeId the node id
660    */
661   private void processInitialState(String nodeId) {
662
663     if (nodeId == null) {
664       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
665       return;
666     }
667
668     ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
669
670     if (cachedNode == null) {
671       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE,
672           "Node cannot be" + " found for nodeId, " + nodeId);
673       return;
674     }
675
676     if (cachedNode.getSelfLink() == null) {
677
678       if (cachedNode.getNodeId() == null) {
679
680         /*
681          * if the self link is null at the INIT state, which could be valid if this node is a
682          * complex attribute group which didn't originate from a self-link, but in that situation
683          * both the node id and node key should already be set.
684          */
685
686         cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
687
688       }
689
690       if (cachedNode.getNodeId() != null) {
691
692         /*
693          * This should be the success path branch if the self-link is not set
694          */
695
696         cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
697             NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
698
699       }
700
701     } else {
702
703       if (cachedNode.hasResolvedSelfLink()) {
704         LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
705         cachedNode.changeState(NodeProcessingState.ERROR,
706             NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
707       } else {
708         cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
709             NodeProcessingAction.SELF_LINK_SET);
710       }
711     }
712   }
713
714   /**
715    * Process skeleton node.
716    *
717    * @param skeletonNode the skeleton node
718    * @param queryParams the query params
719    */
720   private void processSearchableEntity(SearchableEntity searchTargetEntity,
721       QueryParams queryParams) {
722
723     if (searchTargetEntity == null) {
724       return;
725     }
726
727     if (searchTargetEntity.getId() == null) {
728       LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
729           + " node because nodeId is null for node, " + searchTargetEntity.getLink());
730       return;
731     }
732
733     ActiveInventoryNode newNode =
734         new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
735
736     newNode.setNodeId(searchTargetEntity.getId());
737
738     newNode.setNodeDepth(0);
739     newNode.setRootNode(true);
740     LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
741     setRootNodeFound(true);
742
743     newNode.setSelfLink(searchTargetEntity.getLink());
744
745     nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
746   }
747
748   private int getTotalWorkOnHand() {
749
750     int numNodesWithPendingStates = 0;
751
752     if (isRootNodeFound()) {
753       evaluateNodeDepths();
754     }
755
756     for (ActiveInventoryNode n : nodeCache.values()) {
757
758       switch (n.getState()) {
759
760         case READY:
761         case ERROR: {
762           // do nothing, these are our normal
763           // exit states
764           break;
765         }
766
767         default: {
768
769           /*
770            * for all other states, there is work to be done
771            */
772           numNodesWithPendingStates++;
773         }
774
775       }
776
777     }
778     
779     return (aaiWorkOnHand.get() + numNodesWithPendingStates);
780
781   }
782
783   /**
784    * Checks for out standing work.
785    *
786    * @return true, if successful
787    */
788   private void processOutstandingWork(QueryParams queryParams) {
789
790     while (getTotalWorkOnHand() > 0) {
791
792       /*
793        * Force an evaluation of node depths before determining if we should limit state-based
794        * traversal or processing.
795        */
796
797       processCurrentNodeStates(queryParams);
798
799       try {
800         Thread.sleep(10);
801       } catch (InterruptedException exc) {
802         LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
803         return;
804       }
805
806     }
807     
808     dumpThrottledWorkOnHandLog(true);
809
810   }
811
812   /*
813    * (non-Javadoc)
814    * 
815    * @see
816    * org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai.
817    * sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams)
818    */
819   @Override
820   public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
821
822     try {
823
824
825       if (searchtargetEntity == null) {
826         LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
827             contextIdStr + " - Failed to" + " processSelfLinks, searchtargetEntity is null");
828         return;
829       }
830
831       long startTimeInMs = System.currentTimeMillis();
832
833       processSearchableEntity(searchtargetEntity, queryParams);
834
835       /*
836        * This method is blocking until we decouple it with a CountDownLatch await condition, and
837        * make the internal graph processing more event-y.
838        */
839
840       processOutstandingWork(queryParams);
841
842       long totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
843
844       long opTime = System.currentTimeMillis() - startTimeInMs;
845
846       LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
847           String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
848
849     } catch (Exception exc) {
850       LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
851     }
852
853   }
854
855   /**
856    * Verify outbound neighbors.
857    */
858   private void verifyOutboundNeighbors() {
859
860     for (ActiveInventoryNode srcNode : nodeCache.values()) {
861
862       for (String targetNodeId : srcNode.getOutboundNeighbors()) {
863
864         ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
865
866         if (targetNode != null && srcNode.getNodeId() != null) {
867
868           targetNode.addInboundNeighbor(srcNode.getNodeId());
869
870           if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
871             targetNode.addOutboundNeighbor(srcNode.getNodeId());
872           }
873
874         }
875
876       }
877
878     }
879
880   }
881
882   /**
883    * Evaluate node depths.
884    */
885   private void evaluateNodeDepths() {
886
887     int numChanged = -1;
888     int numAttempts = 0;
889
890     while (numChanged != 0) {
891
892       numChanged = 0;
893       numAttempts++;
894
895       for (ActiveInventoryNode srcNode : nodeCache.values()) {
896
897         if (srcNode.getState() == NodeProcessingState.INIT) {
898
899           /*
900            * this maybe the only state that we don't want to to process the node depth on, because
901            * typically it won't have any valid fields set, and it may remain in a partial state
902            * until we have processed the self-link.
903            */
904
905           continue;
906
907         }
908
909         for (String targetNodeId : srcNode.getOutboundNeighbors()) {
910           ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
911
912           if (targetNode != null) {
913
914             if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
915               numChanged++;
916             }
917           }
918         }
919
920         for (String targetNodeId : srcNode.getInboundNeighbors()) {
921           ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
922
923           if (targetNode != null) {
924
925             if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
926               numChanged++;
927             }
928           }
929         }
930       }
931
932       if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
933         LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
934         return;
935       }
936
937     }
938
939     if (LOG.isDebugEnabled()) {
940       if (numAttempts > 0) {
941         LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
942             "Evaluate node depths completed in " + numAttempts + " attempts");
943       } else {
944         LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
945             "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
946       }
947     }
948
949   }
950
951
952   /**
953    * Gets the entity type primary key name.
954    *
955    * @param entityType the entity type
956    * @return the entity type primary key name
957    */
958
959
960   private String getEntityTypePrimaryKeyName(String entityType) {
961
962     if (entityType == null) {
963       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
964           "node primary key" + " name because entity type is null");
965       return null;
966     }
967
968     OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
969
970     if (descriptor == null) {
971       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
972           "oxm entity" + " descriptor for entityType = " + entityType);
973       return null;
974     }
975
976     List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
977
978     if (pkeyNames == null || pkeyNames.size() == 0) {
979       LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
980           "node primary" + " key because descriptor primary key names is empty");
981       return null;
982     }
983
984     return NodeUtils.concatArray(pkeyNames, "/");
985
986   }
987
988 }