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