2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6 * Copyright © 2017 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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
23 package org.onap.aai.sparky.viewandinspect.services;
25 import static java.util.concurrent.CompletableFuture.supplyAsync;
27 import java.net.URISyntaxException;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Iterator;
31 import java.util.List;
33 import java.util.Map.Entry;
34 import java.util.concurrent.ConcurrentHashMap;
35 import java.util.concurrent.ExecutorService;
36 import java.util.concurrent.atomic.AtomicInteger;
38 import org.apache.http.client.utils.URIBuilder;
39 import org.onap.aai.cl.api.Logger;
40 import org.onap.aai.cl.eelf.LoggerFactory;
41 import org.onap.aai.restclient.client.OperationResult;
42 import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
43 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
44 import org.onap.aai.sparky.dal.ActiveInventoryAdapter;
45 import org.onap.aai.sparky.logging.AaiUiMsgs;
46 import org.onap.aai.sparky.sync.entity.SearchableEntity;
47 import org.onap.aai.sparky.util.NodeUtils;
48 import org.onap.aai.sparky.viewandinspect.config.SparkyConstants;
49 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
50 import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
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.entity.Relationship;
55 import org.onap.aai.sparky.viewandinspect.entity.RelationshipData;
56 import org.onap.aai.sparky.viewandinspect.entity.RelationshipList;
57 import org.onap.aai.sparky.viewandinspect.entity.SelfLinkDeterminationTransaction;
58 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction;
59 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState;
60 import org.onap.aai.sparky.viewandinspect.task.PerformNodeSelfLinkProcessingTask;
61 import org.onap.aai.sparky.viewandinspect.task.PerformSelfLinkDeterminationTask;
63 import com.fasterxml.jackson.annotation.JsonInclude.Include;
64 import com.fasterxml.jackson.databind.JsonNode;
65 import com.fasterxml.jackson.databind.ObjectMapper;
66 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
69 * The Class SelfLinkNodeCollector.
71 public class BaseVisualizationContext implements VisualizationContext {
73 private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100;
74 private static final String DEPTH_ALL_MODIFIER = "?depth=all";
75 private static final String NODES_ONLY_MODIFIER = "?nodes-only";
76 private static final String SERVICE_INSTANCE = "service-instance";
78 private static final Logger LOG = LoggerFactory.getInstance().getLogger(
79 BaseVisualizationContext.class);
80 private final ActiveInventoryAdapter aaiAdapter;
82 private int maxSelfLinkTraversalDepth;
83 private AtomicInteger numLinksDiscovered;
84 private AtomicInteger numSuccessfulLinkResolveFromCache;
85 private AtomicInteger numSuccessfulLinkResolveFromFromServer;
86 private AtomicInteger numFailedLinkResolve;
87 private AtomicInteger aaiWorkOnHand;
89 private VisualizationConfigs visualizationConfigs;
91 private AtomicInteger totalLinksRetrieved;
93 private final long contextId;
94 private final String contextIdStr;
96 private ObjectMapper mapper;
97 private InlineMessage inlineMessage = null;
99 private ExecutorService aaiExecutorService;
100 private OxmEntityLookup oxmEntityLookup;
103 * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
104 * re-requesting the same self-links over-and-over again, to speed up the overall render time and
105 * more importantly to reduce the network cost of determining information we already have.
107 private ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
110 * Instantiates a new self link node collector.
112 * @param loader the loader
113 * @throws Exception the exception
115 public BaseVisualizationContext(long contextId, ActiveInventoryAdapter aaiAdapter,
116 ExecutorService aaiExecutorService, VisualizationConfigs visualizationConfigs,
117 OxmEntityLookup oxmEntityLookup)
120 this.contextId = contextId;
121 this.contextIdStr = "[Context-Id=" + contextId + "]";
122 this.aaiAdapter = aaiAdapter;
123 this.aaiExecutorService = aaiExecutorService;
124 this.visualizationConfigs = visualizationConfigs;
125 this.oxmEntityLookup = oxmEntityLookup;
127 this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
128 this.numLinksDiscovered = new AtomicInteger(0);
129 this.totalLinksRetrieved = new AtomicInteger(0);
130 this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
131 this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
132 this.numFailedLinkResolve = new AtomicInteger(0);
133 this.aaiWorkOnHand = new AtomicInteger(0);
135 this.maxSelfLinkTraversalDepth = this.visualizationConfigs.getMaxSelfLinkTraversalDepth();
137 this.mapper = new ObjectMapper();
138 mapper.setSerializationInclusion(Include.NON_EMPTY);
139 mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
142 public long getContextId() {
147 * A utility method for extracting all entity-type primary key values from a provided self-link
148 * and return a set of generic-query API keys.
150 * @param parentEntityType
152 * @return a list of key values that can be used for this entity with the AAI generic-query API
154 protected List<String> extractQueryParamsFromSelfLink(String link) {
156 List<String> queryParams = new ArrayList<String>();
159 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, "self link is null");
163 Map<String, OxmEntityDescriptor> entityDescriptors = oxmEntityLookup.getEntityDescriptors();
167 URIBuilder urlBuilder = new URIBuilder(link);
168 String urlPath = urlBuilder.getPath();
170 OxmEntityDescriptor descriptor = null;
171 String[] urlPathElements = urlPath.split("/");
172 List<String> primaryKeyNames = null;
174 String entityType = null;
176 while (index < urlPathElements.length) {
178 descriptor = entityDescriptors.get(urlPathElements[index]);
180 if (descriptor != null) {
181 entityType = urlPathElements[index];
182 primaryKeyNames = descriptor.getPrimaryKeyAttributeNames();
185 * Make sure from what ever index we matched the parent entity-type on that we can extract
186 * additional path elements for the primary key values.
189 if (index + primaryKeyNames.size() < urlPathElements.length) {
191 for (String primaryKeyName : primaryKeyNames) {
193 queryParams.add(entityType + "." + primaryKeyName + ":" + urlPathElements[index]);
196 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
197 "Could not extract query parametrs for entity-type = '" + entityType
198 + "' from self-link = " + link);
205 } catch (URISyntaxException exc) {
207 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
208 "Error extracting query parameters from self-link = " + link + ". Error = "
217 * Decode complex attribute group.
220 * @param attributeGroup the attribute group
221 * @return boolean indicating whether operation was successful (true), / failure(false).
223 public boolean decodeComplexAttributeGroup(ActiveInventoryNode ain, JsonNode attributeGroup) {
227 Iterator<Entry<String, JsonNode>> entityArrays = attributeGroup.fields();
228 Entry<String, JsonNode> entityArray = null;
230 if (entityArrays == null) {
231 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, attributeGroup.toString());
232 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
236 while (entityArrays.hasNext()) {
238 entityArray = entityArrays.next();
240 String entityType = entityArray.getKey();
241 JsonNode entityArrayObject = entityArray.getValue();
243 if (entityArrayObject.isArray()) {
245 Iterator<JsonNode> entityCollection = entityArrayObject.elements();
246 JsonNode entity = null;
247 while (entityCollection.hasNext()) {
248 entity = entityCollection.next();
250 if (LOG.isDebugEnabled()) {
251 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "decodeComplexAttributeGroup(),"
252 + " entity = " + entity.toString());
256 * Here's what we are going to do:
258 * <li>In the ActiveInventoryNode, on construction maintain a collection of queryParams
259 * that is added to for the purpose of discovering parent->child hierarchies.
261 * <li>When we hit this block of the code then we'll use the queryParams to feed the
262 * generic query to resolve the self-link asynchronously.
264 * <li>Upon successful link determination, then and only then will we create a new node
265 * in the nodeCache and process the child
269 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
270 newNode.setEntityType(entityType);
273 * This is partially a lie because we actually don't have a self-link for complex nodes
274 * discovered in this way.
276 newNode.setSelfLinkProcessed(true);
277 newNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
278 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
281 * copy parent query params into new child
284 if (SERVICE_INSTANCE.equals(entityType)) {
287 * 1707 AAI has an issue being tracked with AAI-8932 where the generic-query cannot be
288 * resolved if all the service-instance path keys are provided. The query only works
289 * if only the service-instance key and valude are passed due to a historical reason.
290 * A fix is being worked on for 1707, and when it becomes available we can revert this
294 newNode.clearQueryParams();
299 * For all other entity-types we want to copy the parent query parameters into the new node
303 for (String queryParam : ain.getQueryParams()) {
304 newNode.addQueryParam(queryParam);
310 if (!addComplexGroupToNode(newNode, entity)) {
311 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, "Failed to add child to parent for child = " + entity.toString());
314 if (!addNodeQueryParams(newNode)) {
315 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Error determining node id and key for node = " + newNode.dumpNodeTree(true)
316 + " skipping relationship processing");
317 newNode.changeState(NodeProcessingState.ERROR,
318 NodeProcessingAction.NODE_IDENTITY_ERROR);
322 newNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
323 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
329 * Order matters for the query params. We need to set the parent ones before the child
333 String selfLinkQuery =
334 aaiAdapter.getGenericQueryForSelfLink(entityType, newNode.getQueryParams());
337 * <li>get the self-link
338 * <li>add it to the new node
339 * <li>generate node id
340 * <li>add node to node cache
341 * <li>add node id to parent outbound links list
342 * <li>process node children (should be automatic) (but don't query and resolve
343 * self-link as we already have all the data)
346 SelfLinkDeterminationTransaction txn = new SelfLinkDeterminationTransaction();
348 txn.setQueryString(selfLinkQuery);
349 txn.setNewNode(newNode);
350 txn.setParentNodeId(ain.getNodeId());
351 aaiWorkOnHand.incrementAndGet();
352 supplyAsync(new PerformSelfLinkDeterminationTask(txn, null, aaiAdapter),
353 aaiExecutorService).whenComplete((nodeTxn, error) -> {
354 aaiWorkOnHand.decrementAndGet();
356 LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_GENERIC, selfLinkQuery);
359 OperationResult opResult = nodeTxn.getOpResult();
361 ActiveInventoryNode newChildNode = txn.getNewNode();
363 if (opResult != null && opResult.wasSuccessful()) {
365 if (!opResult.wasSuccessful()) {
366 numFailedLinkResolve.incrementAndGet();
369 if (opResult.isFromCache()) {
370 numSuccessfulLinkResolveFromCache.incrementAndGet();
372 numSuccessfulLinkResolveFromFromServer.incrementAndGet();
376 * extract the self-link from the operational result.
379 Collection<JsonNode> entityLinks = new ArrayList<JsonNode>();
380 JsonNode genericQueryResult = null;
383 NodeUtils.convertJsonStrToJsonNode(nodeTxn.getOpResult().getResult());
384 } catch (Exception exc) {
385 LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, JsonNode.class.toString(), exc.getMessage());
388 NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link",
391 String selfLink = null;
393 if (entityLinks.size() != 1) {
395 LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS, String.valueOf(entityLinks.size()));
398 selfLink = ((JsonNode) entityLinks.toArray()[0]).asText();
399 selfLink = ActiveInventoryAdapter.extractResourcePath(selfLink);
401 newChildNode.setSelfLink(selfLink);
402 newChildNode.setNodeId(NodeUtils.generateUniqueShaDigest(selfLink));
404 String uri = NodeUtils.calculateEditAttributeUri(selfLink);
406 newChildNode.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
409 ActiveInventoryNode parent = nodeCache.get(txn.getParentNodeId());
411 if (parent != null) {
412 parent.addOutboundNeighbor(newChildNode.getNodeId());
413 newChildNode.addInboundNeighbor(parent.getNodeId());
416 newChildNode.setSelfLinkPendingResolve(false);
417 newChildNode.setSelfLinkProcessed(true);
418 newChildNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
419 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
421 nodeCache.putIfAbsent(newChildNode.getNodeId(), newChildNode);
426 LOG.error(AaiUiMsgs.SELF_LINK_RETRIEVAL_FAILED, txn.getQueryString(),
427 String.valueOf(nodeTxn.getOpResult().getResultCode()), nodeTxn.getOpResult().getResult());
428 newChildNode.setSelflinkRetrievalFailure(true);
429 newChildNode.setSelfLinkProcessed(true);
430 newChildNode.setSelfLinkPendingResolve(false);
432 newChildNode.changeState(NodeProcessingState.ERROR,
433 NodeProcessingAction.SELF_LINK_DETERMINATION_ERROR);
446 LOG.error(AaiUiMsgs.UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE, entityType);
450 } catch (Exception exc) {
451 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Exception caught while"
452 + " decoding complex attribute group - " + exc.getMessage());
460 * Process self link response.
462 * @param nodeId the node id
464 private void processSelfLinkResponse(String nodeId) {
466 if (nodeId == null) {
467 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link"
468 + " response because nodeId is null");
472 ActiveInventoryNode ain = nodeCache.get(nodeId);
475 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link response"
476 + " because can't find node for id = " + nodeId);
480 JsonNode jsonNode = null;
483 jsonNode = mapper.readValue(ain.getOpResult().getResult(), JsonNode.class);
484 } catch (Exception exc) {
485 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
486 + " response str into JsonNode with error, " + exc.getLocalizedMessage());
487 ain.changeState(NodeProcessingState.ERROR,
488 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
492 if (jsonNode == null) {
493 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse json node str."
494 + " Parse resulted a null value.");
495 ain.changeState(NodeProcessingState.ERROR,
496 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
500 Iterator<Entry<String, JsonNode>> fieldNames = jsonNode.fields();
501 Entry<String, JsonNode> field = null;
503 RelationshipList relationshipList = null;
505 while (fieldNames.hasNext()) {
507 field = fieldNames.next();
508 String fieldName = field.getKey();
510 if ("relationship-list".equals(fieldName)) {
513 relationshipList = mapper.readValue(field.getValue().toString(), RelationshipList.class);
515 if (relationshipList != null) {
516 ain.addRelationshipList(relationshipList);
519 } catch (Exception exc) {
520 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse relationship-list"
521 + " attribute. Parse resulted in error, " + exc.getLocalizedMessage());
522 ain.changeState(NodeProcessingState.ERROR,
523 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
529 JsonNode nodeValue = field.getValue();
531 if (nodeValue != null && nodeValue.isValueNode()) {
533 if (oxmEntityLookup.getEntityDescriptors().get(fieldName) == null) {
536 * entity property name is not an entity, thus we can add this property name and value
537 * to our property set
540 ain.addProperty(fieldName, nodeValue.asText());
546 if (nodeValue.isArray()) {
548 if (oxmEntityLookup.getEntityDescriptors().get(fieldName) == null) {
551 * entity property name is not an entity, thus we can add this property name and value
552 * to our property set
555 ain.addProperty(field.getKey(), nodeValue.toString());
561 ain.addComplexGroup(nodeValue);
570 String uri = NodeUtils.calculateEditAttributeUri(ain.getSelfLink());
572 ain.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
576 * We need a special behavior for intermediate entities from the REST model
578 * Tenants are not top level entities, and when we want to visualization
579 * their children, we need to construct keys that include the parent entity query
580 * keys, the current entity type keys, and the child keys. We'll always have the
581 * current entity and children, but never the parent entity in the current (1707) REST
584 * We have two possible solutions:
586 * 1) Try to use the custom-query approach to learn about the entity keys
587 * - this could be done, but it could be very expensive for large objects. When we do the first
588 * query to get a tenant, it will list all the in and out edges related to this entity,
589 * there is presently no way to filter this. But the approach could be made to work and it would be
590 * somewhat data-model driven, other than the fact that we have to first realize that the entity
591 * that is being searched for is not top-level entity. Once we have globally unique ids for resources
592 * this logic will not be needed and everything will be simpler. The only reason we are in this logic
593 * at all is to be able to calculate a url for the child entities so we can hash it to generate
594 * a globally unique id that can be safely used for the node.
596 * *2* Extract the keys from the pathed self-link.
597 * This is a bad solution and I don't like it but it will be fast for all resource types, as the
598 * information is already encoded in the URI. When we get to a point where we switch to a better
599 * globally unique entity identity model, then a lot of the code being used to calculate an entity url
600 * to in-turn generate a deterministic globally unique id will disappear.
603 * right now we have the following:
605 * - cloud-regions/cloud-region/{cloud-region-id}/{cloud-owner-id}/tenants/tenant/{tenant-id}
610 * For all entity types use the self-link extraction method to be consistent. Once we have a
611 * globally unique identity mechanism for entities, this logic can be revisited.
613 ain.clearQueryParams();
614 ain.addQueryParams(extractQueryParamsFromSelfLink(ain.getSelfLink()));
615 ain.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
616 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
622 * Perform self link resolve.
624 * @param nodeId the node id
626 private void performSelfLinkResolve(String nodeId) {
628 if (nodeId == null) {
629 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Resolve of self-link"
630 + " has been skipped because provided nodeId is null");
634 ActiveInventoryNode ain = nodeCache.get(nodeId);
637 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId
638 + ", from node cache. Resolve self-link method has been skipped.");
642 if (!ain.isSelfLinkPendingResolve()) {
644 ain.setSelfLinkPendingResolve(true);
646 // kick off async self-link resolution
648 if (LOG.isDebugEnabled()) {
649 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
650 "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink());
653 numLinksDiscovered.incrementAndGet();
655 String depthModifier = DEPTH_ALL_MODIFIER;
658 * If the current node is the search target, we want to see everything the node has to offer
659 * from the self-link and not filter it to a single node.
662 if (visualizationConfigs.getShallowEntities().contains(ain.getEntityType())
663 && !ain.isRootNode()) {
664 depthModifier = NODES_ONLY_MODIFIER;
667 NodeProcessingTransaction txn = new NodeProcessingTransaction();
668 txn.setProcessingNode(ain);
669 txn.setRequestParameters(depthModifier);
670 aaiWorkOnHand.incrementAndGet();
672 new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiAdapter),
673 aaiExecutorService).whenComplete((nodeTxn, error) -> {
674 aaiWorkOnHand.decrementAndGet();
678 * an error processing the self link should probably result in the node processing
679 * state shifting to ERROR
682 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
684 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
685 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
687 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
691 totalLinksRetrieved.incrementAndGet();
693 OperationResult opResult = nodeTxn.getOpResult();
695 if (opResult != null && opResult.wasSuccessful()) {
697 if (!opResult.wasSuccessful()) {
698 numFailedLinkResolve.incrementAndGet();
701 if (opResult.isFromCache()) {
702 numSuccessfulLinkResolveFromCache.incrementAndGet();
704 numSuccessfulLinkResolveFromFromServer.incrementAndGet();
708 nodeTxn.getProcessingNode().setOpResult(opResult);
709 nodeTxn.getProcessingNode().changeState(
710 NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
711 NodeProcessingAction.SELF_LINK_RESOLVE_OK);
713 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
714 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
717 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Self Link retrieval for link,"
718 + txn.getSelfLinkWithModifiers() + ", failed with error code,"
719 + nodeTxn.getOpResult().getResultCode() + ", and message,"
720 + nodeTxn.getOpResult().getResult());
722 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
723 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
725 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
726 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
728 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
743 * @param nodeId the node id
745 private void processNeighbors(String nodeId) {
747 if (nodeId == null) {
748 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
749 + " neighbors because nodeId is null.");
753 ActiveInventoryNode ain = nodeCache.get(nodeId);
756 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
757 + " neighbors because node could not be found in nodeCache with id, " + nodeId);
762 * process complex attribute and relationships
765 boolean neighborsProcessedSuccessfully = true;
767 for (JsonNode n : ain.getComplexGroups()) {
768 neighborsProcessedSuccessfully &= decodeComplexAttributeGroup(ain, n);
771 for (RelationshipList relationshipList : ain.getRelationshipLists()) {
772 neighborsProcessedSuccessfully &= addSelfLinkRelationshipChildren(ain, relationshipList);
776 if (neighborsProcessedSuccessfully) {
777 ain.changeState(NodeProcessingState.READY, NodeProcessingAction.NEIGHBORS_PROCESSED_OK);
779 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
784 * If neighbors fail to process, there is already a call to change the state within the
785 * relationship and neighbor processing functions.
791 * Find and mark root node.
793 * @param queryParams the query params
794 * @return true, if successful
796 private boolean findAndMarkRootNode(QueryParams queryParams) {
798 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
800 if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
801 cacheNode.setNodeDepth(0);
802 cacheNode.setRootNode(true);
803 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
813 * Process current node states.
815 * @param rootNodeDiscovered the root node discovered
817 private void processCurrentNodeStates(boolean rootNodeDiscovered) {
819 * Force an evaluation of node depths before determining if we should limit state-based
820 * traversal or processing.
822 if (rootNodeDiscovered) {
823 evaluateNodeDepths();
826 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
828 if (LOG.isDebugEnabled()) {
829 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
830 "processCurrentNodeState(), nid = "
831 + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
834 switch (cacheNode.getState()) {
837 processInitialState(cacheNode.getNodeId());
846 case SELF_LINK_UNRESOLVED: {
847 performSelfLinkResolve(cacheNode.getNodeId());
851 case SELF_LINK_RESPONSE_UNPROCESSED: {
852 processSelfLinkResponse(cacheNode.getNodeId());
856 case NEIGHBORS_UNPROCESSED: {
859 * We use the rootNodeDiscovered flag to ignore depth retrieval thresholds until the root
860 * node is identified. Then the evaluative depth calculations should re-balance the graph
861 * around the root node.
864 if (!rootNodeDiscovered || cacheNode.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
866 if (LOG.isDebugEnabled()) {
867 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
868 "SLNC::processCurrentNodeState() -- Node at max depth,"
869 + " halting processing at current state = -- "
870 + cacheNode.getState() + " nodeId = " + cacheNode.getNodeId());
875 processNeighbors(cacheNode.getNodeId());
893 * Adds the complex group to node.
895 * @param targetNode the target node
896 * @param attributeGroup the attribute group
897 * @return true, if successful
899 private boolean addComplexGroupToNode(ActiveInventoryNode targetNode, JsonNode attributeGroup) {
901 if (attributeGroup == null) {
902 targetNode.changeState(NodeProcessingState.ERROR,
903 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
907 RelationshipList relationshipList = null;
909 if (attributeGroup.isObject()) {
911 Iterator<Entry<String, JsonNode>> fields = attributeGroup.fields();
912 Entry<String, JsonNode> field = null;
916 while (fields.hasNext()) {
917 field = fields.next();
918 fieldName = field.getKey();
919 fieldValue = field.getValue();
921 if (fieldValue.isObject()) {
923 if (fieldName.equals("relationship-list")) {
927 mapper.readValue(field.getValue().toString(), RelationshipList.class);
929 if (relationshipList != null) {
930 targetNode.addRelationshipList(relationshipList);
933 } catch (Exception exc) {
934 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse"
935 + " relationship-list attribute. Parse resulted in error, "
936 + exc.getLocalizedMessage());
937 targetNode.changeState(NodeProcessingState.ERROR,
938 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_ERROR);
943 targetNode.addComplexGroup(fieldValue);
946 } else if (fieldValue.isArray()) {
947 if (LOG.isDebugEnabled()) {
948 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
949 "Unexpected array type with a key = " + fieldName);
951 } else if (fieldValue.isValueNode()) {
952 if (oxmEntityLookup.getEntityDescriptors().get(field.getKey()) == null) {
954 * property key is not an entity type, add it to our property set.
956 targetNode.addProperty(field.getKey(), fieldValue.asText());
962 } else if (attributeGroup.isArray()) {
963 if (LOG.isDebugEnabled()) {
964 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
965 "Unexpected array type for attributeGroup = " + attributeGroup);
967 } else if (attributeGroup.isValueNode()) {
968 if (LOG.isDebugEnabled()) {
969 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
970 "Unexpected value type for attributeGroup = " + attributeGroup);
977 public int getNumSuccessfulLinkResolveFromCache() {
978 return numSuccessfulLinkResolveFromCache.get();
981 public int getNumSuccessfulLinkResolveFromFromServer() {
982 return numSuccessfulLinkResolveFromFromServer.get();
985 public int getNumFailedLinkResolve() {
986 return numFailedLinkResolve.get();
989 public InlineMessage getInlineMessage() {
990 return inlineMessage;
993 public void setInlineMessage(InlineMessage inlineMessage) {
994 this.inlineMessage = inlineMessage;
997 public void setMaxSelfLinkTraversalDepth(int depth) {
998 this.maxSelfLinkTraversalDepth = depth;
1001 public int getMaxSelfLinkTraversalDepth() {
1002 return this.maxSelfLinkTraversalDepth;
1005 public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
1010 * Gets the relationship primary key values.
1013 * @param entityType the entity type
1014 * @param pkeyNames the pkey names
1015 * @return the relationship primary key values
1017 private String getRelationshipPrimaryKeyValues(Relationship r, String entityType,
1018 List<String> pkeyNames) {
1020 StringBuilder sb = new StringBuilder(64);
1022 if (pkeyNames.size() > 0) {
1023 String primaryKey = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(0));
1024 if (primaryKey != null) {
1026 sb.append(primaryKey);
1029 // this should be a fatal error because unless we can
1030 // successfully retrieve all the expected keys we'll end up
1031 // with a garbage node
1032 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract"
1033 + " keyName, " + entityType + "." + pkeyNames.get(0)
1034 + ", from relationship data, " + r.toString());
1038 for (int i = 1; i < pkeyNames.size(); i++) {
1040 String kv = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(i));
1042 sb.append("/").append(kv);
1044 // this should be a fatal error because unless we can
1045 // successfully retrieve all the expected keys we'll end up
1046 // with a garbage node
1047 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: failed to extract keyName, "
1048 + entityType + "." + pkeyNames.get(i)
1049 + ", from relationship data, " + r.toString());
1054 return sb.toString();
1063 * Extract key value from relation data.
1066 * @param keyName the key name
1067 * @return the string
1069 private String extractKeyValueFromRelationData(Relationship r, String keyName) {
1071 RelationshipData[] rdList = r.getRelationshipData();
1073 for (RelationshipData relData : rdList) {
1075 if (relData.getRelationshipKey().equals(keyName)) {
1076 return relData.getRelationshipValue();
1084 * Determine node id and key.
1086 * @param ain the ain
1087 * @return true, if successful
1089 private boolean addNodeQueryParams(ActiveInventoryNode ain) {
1092 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "ActiveInventoryNode is null");
1096 List<String> pkeyNames =
1097 oxmEntityLookup.getEntityDescriptors().get(ain.getEntityType()).getPrimaryKeyAttributeNames();
1099 if (pkeyNames == null || pkeyNames.size() == 0) {
1100 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Primary key names is empty");
1104 StringBuilder sb = new StringBuilder(64);
1106 if (pkeyNames.size() > 0) {
1107 String primaryKey = ain.getProperties().get(pkeyNames.get(0));
1108 if (primaryKey != null) {
1109 sb.append(primaryKey);
1111 // this should be a fatal error because unless we can
1112 // successfully retrieve all the expected keys we'll end up
1113 // with a garbage node
1114 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
1115 + pkeyNames.get(0) + ", from entity properties");
1119 for (int i = 1; i < pkeyNames.size(); i++) {
1121 String kv = ain.getProperties().get(pkeyNames.get(i));
1123 sb.append("/").append(kv);
1125 // this should be a fatal error because unless we can
1126 // successfully retrieve all the expected keys we'll end up
1127 // with a garbage node
1128 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
1129 + pkeyNames.get(i) + ", from entity properties");
1134 /*final String nodeId = NodeUtils.generateUniqueShaDigest(ain.getEntityType(),
1135 NodeUtils.concatArray(pkeyNames, "/"), sb.toString());*/
1137 //ain.setNodeId(nodeId);
1138 ain.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1139 ain.setPrimaryKeyValue(sb.toString());
1141 if (ain.getEntityType() != null && ain.getPrimaryKeyName() != null
1142 && ain.getPrimaryKeyValue() != null) {
1144 ain.getEntityType() + "." + ain.getPrimaryKeyName() + ":" + ain.getPrimaryKeyValue());
1155 * Adds the self link relationship children.
1157 * @param processingNode the processing node
1158 * @param relationshipList the relationship list
1159 * @return true, if successful
1161 private boolean addSelfLinkRelationshipChildren(ActiveInventoryNode processingNode,
1162 RelationshipList relationshipList) {
1164 if (relationshipList == null) {
1165 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "No relationships added to parent node = "
1166 + processingNode.getNodeId() + " because relationshipList is empty");
1167 processingNode.changeState(NodeProcessingState.ERROR,
1168 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1172 Relationship[] relationshipArray = relationshipList.getRelationshipList();
1173 OxmEntityDescriptor descriptor = null;
1174 String repairedSelfLink = null;
1176 if (relationshipArray != null) {
1178 ActiveInventoryNode newNode = null;
1179 String resourcePath = null;
1181 for (Relationship r : relationshipArray) {
1183 resourcePath = ActiveInventoryAdapter.extractResourcePath(r.getRelatedLink());
1185 String nodeId = NodeUtils.generateUniqueShaDigest(resourcePath);
1187 if (nodeId == null) {
1189 LOG.error(AaiUiMsgs.SKIPPING_RELATIONSHIP, r.toString());
1190 processingNode.changeState(NodeProcessingState.ERROR,
1191 NodeProcessingAction.NODE_IDENTITY_ERROR);
1195 newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
1197 String entityType = r.getRelatedTo();
1199 if (r.getRelationshipData() != null) {
1200 for (RelationshipData rd : r.getRelationshipData()) {
1201 newNode.addQueryParam(rd.getRelationshipKey() + ":" + rd.getRelationshipValue());
1205 descriptor = oxmEntityLookup.getEntityDescriptors().get(r.getRelatedTo());
1207 newNode.setNodeId(nodeId);
1208 newNode.setEntityType(entityType);
1209 newNode.setSelfLink(resourcePath);
1211 processingNode.addOutboundNeighbor(nodeId);
1213 if (descriptor != null) {
1215 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1217 newNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1218 NodeProcessingAction.SELF_LINK_SET);
1220 newNode.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1222 String primaryKeyValues = getRelationshipPrimaryKeyValues(r, entityType, pkeyNames);
1223 newNode.setPrimaryKeyValue(primaryKeyValues);
1227 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR,
1228 "Failed to parse entity because OXM descriptor could not be found for type = "
1229 + r.getRelatedTo());
1231 newNode.changeState(NodeProcessingState.ERROR,
1232 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1236 if (nodeCache.putIfAbsent(nodeId, newNode) != null) {
1237 if (LOG.isDebugEnabled()) {
1238 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1239 "Failed to add node to nodeCache because it already exists. Node id = "
1240 + newNode.getNodeId());
1253 * Process initial state.
1255 * @param nodeId the node id
1257 private void processInitialState(String nodeId) {
1259 if (nodeId == null) {
1260 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
1264 ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
1266 if (cachedNode == null) {
1267 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node cannot be"
1268 + " found for nodeId, " + nodeId);
1272 if (cachedNode.getSelfLink() == null) {
1274 if (cachedNode.getNodeId() == null ) {
1277 * if the self link is null at the INIT state, which could be valid if this node is a
1278 * complex attribute group which didn't originate from a self-link, but in that situation
1279 * both the node id and node key should already be set.
1282 cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
1286 if (cachedNode.getNodeId() != null) {
1289 * This should be the success path branch if the self-link is not set
1292 cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
1293 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
1299 if (cachedNode.hasResolvedSelfLink()) {
1300 LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
1301 cachedNode.changeState(NodeProcessingState.ERROR,
1302 NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
1304 cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1305 NodeProcessingAction.SELF_LINK_SET);
1311 * Process skeleton node.
1313 * @param skeletonNode the skeleton node
1314 * @param queryParams the query params
1316 private void processSearchableEntity(SearchableEntity searchTargetEntity, QueryParams queryParams) {
1318 if (searchTargetEntity == null) {
1322 if (searchTargetEntity.getId() == null) {
1323 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
1324 + " node because nodeId is null for node, " + searchTargetEntity.getLink());
1328 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
1330 newNode.setNodeId(searchTargetEntity.getId());
1331 newNode.setEntityType(searchTargetEntity.getEntityType());
1332 newNode.setPrimaryKeyName(getEntityTypePrimaryKeyName(searchTargetEntity.getEntityType()));
1333 newNode.setPrimaryKeyValue(searchTargetEntity.getEntityPrimaryKeyValue());
1335 if (newNode.getEntityType() != null && newNode.getPrimaryKeyName() != null
1336 && newNode.getPrimaryKeyValue() != null) {
1337 newNode.addQueryParam(
1338 newNode.getEntityType() + "." + newNode.getPrimaryKeyName() + ":" + newNode.getPrimaryKeyValue());
1341 * This code may need some explanation. In any graph there will be a single root node. The root
1342 * node is really the center of the universe, and for now, we are tagging the search target as
1343 * the root node. Everything else in the visualization of the graph will be centered around this
1344 * node as the focal point of interest.
1346 * Due to it's special nature, there will only ever be one root node, and it's node depth will
1347 * always be equal to zero.
1350 if (queryParams.getSearchTargetNodeId().equals(newNode.getNodeId())) {
1351 newNode.setNodeDepth(0);
1352 newNode.setRootNode(true);
1353 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
1356 newNode.setSelfLink(searchTargetEntity.getLink());
1358 nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
1362 * Checks for out standing work.
1364 * @return true, if successful
1366 private boolean hasOutStandingWork() {
1368 int numNodesWithPendingStates = 0;
1371 * Force an evaluation of node depths before determining if we should limit state-based
1372 * traversal or processing.
1375 evaluateNodeDepths();
1377 for (ActiveInventoryNode n : nodeCache.values()) {
1379 switch (n.getState()) {
1383 // do nothing, these are our normal
1388 case NEIGHBORS_UNPROCESSED: {
1390 if (n.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
1392 * Only process our neighbors relationships if our current depth is less than the max
1395 numNodesWithPendingStates++;
1404 * for all other states, there is work to be done
1406 numNodesWithPendingStates++;
1413 LOG.debug(AaiUiMsgs.OUTSTANDING_WORK_PENDING_NODES, String.valueOf(numNodesWithPendingStates));
1415 return (numNodesWithPendingStates > 0);
1420 * @see org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai.sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams)
1423 public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
1427 if (searchtargetEntity == null) {
1428 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, contextIdStr + " - Failed to"
1429 + " processSelfLinks, searchtargetEntity is null");
1433 processSearchableEntity(searchtargetEntity, queryParams);
1435 long startTimeInMs = System.currentTimeMillis();
1438 * wait until all transactions are complete or guard-timer expires.
1441 long totalResolveTime = 0;
1442 boolean hasOutstandingWork = hasOutStandingWork();
1443 boolean outstandingWorkGuardTimerFired = false;
1444 long maxGuardTimeInMs = 5000;
1445 long guardTimeInMs = 0;
1446 boolean foundRootNode = false;
1450 * TODO: Put a count-down-latch in place of the while loop, but if we do that then
1451 * we'll need to decouple the visualization processing from the main thread so it can continue to process while
1452 * the main thread is waiting on for count-down-latch gate to open. This may also be easier once we move to the
1453 * VisualizationService + VisualizationContext ideas.
1457 while (hasOutstandingWork || !outstandingWorkGuardTimerFired) {
1459 if (!foundRootNode) {
1460 foundRootNode = findAndMarkRootNode(queryParams);
1463 processCurrentNodeStates(foundRootNode);
1465 verifyOutboundNeighbors();
1469 } catch (InterruptedException exc) {
1470 LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
1474 totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
1476 if (!hasOutstandingWork) {
1478 guardTimeInMs += 500;
1480 if (guardTimeInMs > maxGuardTimeInMs) {
1481 outstandingWorkGuardTimerFired = true;
1487 hasOutstandingWork = hasOutStandingWork();
1491 long opTime = System.currentTimeMillis() - startTimeInMs;
1493 LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
1494 String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
1496 } catch (Exception exc) {
1497 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
1503 * Verify outbound neighbors.
1505 private void verifyOutboundNeighbors() {
1507 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1509 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1511 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1513 if (targetNode != null && srcNode.getNodeId() != null) {
1515 targetNode.addInboundNeighbor(srcNode.getNodeId());
1517 if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
1518 targetNode.addOutboundNeighbor(srcNode.getNodeId());
1530 * Evaluate node depths.
1532 private void evaluateNodeDepths() {
1534 int numChanged = -1;
1535 int numAttempts = 0;
1537 while (numChanged != 0) {
1542 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1544 if (srcNode.getState() == NodeProcessingState.INIT) {
1547 * this maybe the only state that we don't want to to process the node depth on, because
1548 * typically it won't have any valid fields set, and it may remain in a partial state
1549 * until we have processed the self-link.
1556 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1557 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1559 if (targetNode != null) {
1561 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1567 for (String targetNodeId : srcNode.getInboundNeighbors()) {
1568 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1570 if (targetNode != null) {
1572 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1579 if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
1580 LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
1586 if (LOG.isDebugEnabled()) {
1587 if (numAttempts > 0) {
1588 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1589 "Evaluate node depths completed in " + numAttempts + " attempts");
1591 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1592 "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
1600 * Gets the entity type primary key name.
1602 * @param entityType the entity type
1603 * @return the entity type primary key name
1607 private String getEntityTypePrimaryKeyName(String entityType) {
1609 if (entityType == null) {
1610 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary key"
1611 + " name because entity type is null");
1615 OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
1617 if (descriptor == null) {
1618 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "oxm entity"
1619 + " descriptor for entityType = " + entityType);
1623 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1625 if (pkeyNames == null || pkeyNames.size() == 0) {
1626 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary"
1627 + " key because descriptor primary key names is empty");
1631 return NodeUtils.concatArray(pkeyNames, "/");