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