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