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