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