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.config.oxm.OxmModelLoader;
45 import org.onap.aai.sparky.dal.ActiveInventoryAdapter;
46 import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig;
47 import org.onap.aai.sparky.logging.AaiUiMsgs;
48 import org.onap.aai.sparky.sync.entity.SearchableEntity;
49 import org.onap.aai.sparky.util.NodeUtils;
50 import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants;
51 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
52 import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
53 import org.onap.aai.sparky.viewandinspect.entity.InlineMessage;
54 import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction;
55 import org.onap.aai.sparky.viewandinspect.entity.QueryParams;
56 import org.onap.aai.sparky.viewandinspect.entity.Relationship;
57 import org.onap.aai.sparky.viewandinspect.entity.RelationshipData;
58 import org.onap.aai.sparky.viewandinspect.entity.RelationshipList;
59 import org.onap.aai.sparky.viewandinspect.entity.SelfLinkDeterminationTransaction;
60 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction;
61 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState;
62 import org.onap.aai.sparky.viewandinspect.task.PerformNodeSelfLinkProcessingTask;
63 import org.onap.aai.sparky.viewandinspect.task.PerformSelfLinkDeterminationTask;
65 import com.fasterxml.jackson.annotation.JsonInclude.Include;
66 import com.fasterxml.jackson.databind.JsonNode;
67 import com.fasterxml.jackson.databind.ObjectMapper;
68 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
71 * The Class SelfLinkNodeCollector.
73 public class VisualizationContext {
75 private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100;
76 private static final String DEPTH_ALL_MODIFIER = "?depth=all";
77 private static final String NODES_ONLY_MODIFIER = "?nodes-only";
78 private static final String SERVICE_INSTANCE = "service-instance";
80 private static final Logger LOG =
81 LoggerFactory.getInstance().getLogger(VisualizationContext.class);
82 private final ActiveInventoryAdapter aaiAdapter;
84 private int maxSelfLinkTraversalDepth;
85 private AtomicInteger numLinksDiscovered;
86 private AtomicInteger numSuccessfulLinkResolveFromCache;
87 private AtomicInteger numSuccessfulLinkResolveFromFromServer;
88 private AtomicInteger numFailedLinkResolve;
89 private AtomicInteger nodeIntegrityWorkOnHand;
90 private AtomicInteger aaiWorkOnHand;
92 private ActiveInventoryConfig aaiConfig;
93 private VisualizationConfigs visualizationConfigs;
94 private List<String> shallowEntities;
96 private AtomicInteger totalLinksRetrieved;
98 private final long contextId;
99 private final String contextIdStr;
101 private OxmModelLoader loader;
102 private ObjectMapper mapper;
103 private InlineMessage inlineMessage = null;
105 private ExecutorService tabularExecutorService;
106 private ExecutorService aaiExecutorService;
109 * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
110 * re-requesting the same self-links over-and-over again, to speed up the overall render time and
111 * more importantly to reduce the network cost of determining information we already have.
113 private ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
116 * Instantiates a new self link node collector.
118 * @param loader the loader
119 * @throws Exception the exception
121 public VisualizationContext(long contextId, ActiveInventoryAdapter aaiAdapter,
122 ExecutorService tabularExecutorService, ExecutorService aaiExecutorService,
123 VisualizationConfigs visualizationConfigs) throws Exception {
125 this.contextId = contextId;
126 this.contextIdStr = "[Context-Id=" + contextId + "]";
127 this.aaiAdapter = aaiAdapter;
128 this.tabularExecutorService = tabularExecutorService;
129 this.aaiExecutorService = aaiExecutorService;
130 this.visualizationConfigs = visualizationConfigs;
132 this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
133 this.numLinksDiscovered = new AtomicInteger(0);
134 this.totalLinksRetrieved = new AtomicInteger(0);
135 this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
136 this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
137 this.numFailedLinkResolve = new AtomicInteger(0);
138 this.nodeIntegrityWorkOnHand = new AtomicInteger(0);
139 this.aaiWorkOnHand = new AtomicInteger(0);
141 this.aaiConfig = ActiveInventoryConfig.getConfig();
142 this.shallowEntities = aaiConfig.getAaiRestConfig().getShallowEntities();
144 this.maxSelfLinkTraversalDepth = this.visualizationConfigs.getMaxSelfLinkTraversalDepth();
146 this.mapper = new ObjectMapper();
147 mapper.setSerializationInclusion(Include.NON_EMPTY);
148 mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
151 public long getContextId() {
156 * A utility method for extracting all entity-type primary key values from a provided self-link
157 * and return a set of generic-query API keys.
159 * @param parentEntityType
161 * @return a list of key values that can be used for this entity with the AAI generic-query API
163 protected List<String> extractQueryParamsFromSelfLink(String link) {
165 List<String> queryParams = new ArrayList<String>();
168 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, "self link is null");
172 Map<String, OxmEntityDescriptor> entityDescriptors =
173 OxmEntityLookup.getInstance().getEntityDescriptors();
177 URIBuilder urlBuilder = new URIBuilder(link);
178 String urlPath = urlBuilder.getPath();
180 OxmEntityDescriptor descriptor = null;
181 String[] urlPathElements = urlPath.split("/");
182 List<String> primaryKeyNames = null;
184 String entityType = null;
186 while (index < urlPathElements.length) {
188 descriptor = entityDescriptors.get(urlPathElements[index]);
190 if (descriptor != null) {
191 entityType = urlPathElements[index];
192 primaryKeyNames = descriptor.getPrimaryKeyAttributeNames();
195 * Make sure from what ever index we matched the parent entity-type on that we can extract
196 * additional path elements for the primary key values.
199 if (index + primaryKeyNames.size() < urlPathElements.length) {
201 for (String primaryKeyName : primaryKeyNames) {
203 queryParams.add(entityType + "." + primaryKeyName + ":" + urlPathElements[index]);
206 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
207 "Could not extract query parametrs for entity-type = '" + entityType
208 + "' from self-link = " + link);
215 } catch (URISyntaxException exc) {
217 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
218 "Error extracting query parameters from self-link = " + link + ". Error = "
227 * Decode complex attribute group.
230 * @param attributeGroup the attribute group
231 * @return boolean indicating whether operation was successful (true), / failure(false).
233 public boolean decodeComplexAttributeGroup(ActiveInventoryNode ain, JsonNode attributeGroup) {
237 Iterator<Entry<String, JsonNode>> entityArrays = attributeGroup.fields();
238 Entry<String, JsonNode> entityArray = null;
240 if (entityArrays == null) {
241 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, attributeGroup.toString());
242 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
246 while (entityArrays.hasNext()) {
248 entityArray = entityArrays.next();
250 String entityType = entityArray.getKey();
251 JsonNode entityArrayObject = entityArray.getValue();
253 if (entityArrayObject.isArray()) {
255 Iterator<JsonNode> entityCollection = entityArrayObject.elements();
256 JsonNode entity = null;
257 while (entityCollection.hasNext()) {
258 entity = entityCollection.next();
260 if (LOG.isDebugEnabled()) {
261 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
262 "decodeComplexAttributeGroup()," + " entity = " + entity.toString());
266 * Here's what we are going to do:
268 * <li>In the ActiveInventoryNode, on construction maintain a collection of queryParams
269 * that is added to for the purpose of discovering parent->child hierarchies.
271 * <li>When we hit this block of the code then we'll use the queryParams to feed the
272 * generic query to resolve the self-link asynchronously.
274 * <li>Upon successful link determination, then and only then will we create a new node
275 * in the nodeCache and process the child
279 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs);
280 newNode.setEntityType(entityType);
283 * This is partially a lie because we actually don't have a self-link for complex nodes
284 * discovered in this way.
286 newNode.setSelfLinkProcessed(true);
287 newNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
288 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
291 * copy parent query params into new child
294 if (SERVICE_INSTANCE.equals(entityType)) {
297 * 1707 AAI has an issue being tracked with AAI-8932 where the generic-query cannot be
298 * resolved if all the service-instance path keys are provided. The query only works
299 * if only the service-instance key and valude are passed due to a historical reason.
300 * A fix is being worked on for 1707, and when it becomes available we can revert this
304 newNode.clearQueryParams();
309 * For all other entity-types we want to copy the parent query parameters into the new
310 * node query parameters.
313 for (String queryParam : ain.getQueryParams()) {
314 newNode.addQueryParam(queryParam);
320 if (!addComplexGroupToNode(newNode, entity)) {
321 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE,
322 "Failed to add child to parent for child = " + entity.toString());
325 if (!addNodeQueryParams(newNode)) {
326 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID,
327 "Error determining node id and key for node = " + newNode.dumpNodeTree(true)
328 + " skipping relationship processing");
329 newNode.changeState(NodeProcessingState.ERROR,
330 NodeProcessingAction.NODE_IDENTITY_ERROR);
334 newNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
335 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
341 * Order matters for the query params. We need to set the parent ones before the child
345 String selfLinkQuery =
346 aaiAdapter.getGenericQueryForSelfLink(entityType, newNode.getQueryParams());
349 * <li>get the self-link
350 * <li>add it to the new node
351 * <li>generate node id
352 * <li>add node to node cache
353 * <li>add node id to parent outbound links list
354 * <li>process node children (should be automatic) (but don't query and resolve
355 * self-link as we already have all the data)
358 SelfLinkDeterminationTransaction txn = new SelfLinkDeterminationTransaction();
360 txn.setQueryString(selfLinkQuery);
361 txn.setNewNode(newNode);
362 txn.setParentNodeId(ain.getNodeId());
363 aaiWorkOnHand.incrementAndGet();
364 supplyAsync(new PerformSelfLinkDeterminationTask(txn, null, aaiAdapter),
365 aaiExecutorService).whenComplete((nodeTxn, error) -> {
366 aaiWorkOnHand.decrementAndGet();
368 LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_GENERIC, selfLinkQuery);
371 OperationResult opResult = nodeTxn.getOpResult();
373 ActiveInventoryNode newChildNode = txn.getNewNode();
375 if (opResult != null && opResult.wasSuccessful()) {
377 if (!opResult.wasSuccessful()) {
378 numFailedLinkResolve.incrementAndGet();
381 if (opResult.isFromCache()) {
382 numSuccessfulLinkResolveFromCache.incrementAndGet();
384 numSuccessfulLinkResolveFromFromServer.incrementAndGet();
388 * extract the self-link from the operational result.
391 Collection<JsonNode> entityLinks = new ArrayList<JsonNode>();
392 JsonNode genericQueryResult = null;
395 NodeUtils.convertJsonStrToJsonNode(nodeTxn.getOpResult().getResult());
396 } catch (Exception exc) {
397 LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, JsonNode.class.toString(),
401 NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link",
404 String selfLink = null;
406 if (entityLinks.size() != 1) {
408 LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS,
409 String.valueOf(entityLinks.size()));
412 selfLink = ((JsonNode) entityLinks.toArray()[0]).asText();
413 selfLink = ActiveInventoryConfig.extractResourcePath(selfLink);
415 newChildNode.setSelfLink(selfLink);
416 newChildNode.setNodeId(NodeUtils.generateUniqueShaDigest(selfLink));
418 String uri = NodeUtils.calculateEditAttributeUri(selfLink);
420 newChildNode.addProperty(TierSupportUiConstants.URI_ATTR_NAME, uri);
423 ActiveInventoryNode parent = nodeCache.get(txn.getParentNodeId());
425 if (parent != null) {
426 parent.addOutboundNeighbor(newChildNode.getNodeId());
427 newChildNode.addInboundNeighbor(parent.getNodeId());
430 newChildNode.setSelfLinkPendingResolve(false);
431 newChildNode.setSelfLinkProcessed(true);
432 newChildNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
433 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
435 nodeCache.putIfAbsent(newChildNode.getNodeId(), newChildNode);
440 LOG.error(AaiUiMsgs.SELF_LINK_RETRIEVAL_FAILED, txn.getQueryString(),
441 String.valueOf(nodeTxn.getOpResult().getResultCode()),
442 nodeTxn.getOpResult().getResult());
443 newChildNode.setSelflinkRetrievalFailure(true);
444 newChildNode.setSelfLinkProcessed(true);
445 newChildNode.setSelfLinkPendingResolve(false);
447 newChildNode.changeState(NodeProcessingState.ERROR,
448 NodeProcessingAction.SELF_LINK_DETERMINATION_ERROR);
461 LOG.error(AaiUiMsgs.UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE, entityType);
465 } catch (Exception exc) {
466 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
467 "Exception caught while" + " decoding complex attribute group - " + exc.getMessage());
475 * Process self link response.
477 * @param nodeId the node id
479 private void processSelfLinkResponse(String nodeId) {
481 if (nodeId == null) {
482 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
483 "Cannot process self link" + " response because nodeId is null");
487 ActiveInventoryNode ain = nodeCache.get(nodeId);
490 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
491 "Cannot process self link response" + " because can't find node for id = " + nodeId);
495 JsonNode jsonNode = null;
498 jsonNode = mapper.readValue(ain.getOpResult().getResult(), JsonNode.class);
499 } catch (Exception exc) {
500 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
501 + " response str into JsonNode with error, " + exc.getLocalizedMessage());
502 ain.changeState(NodeProcessingState.ERROR,
503 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
507 if (jsonNode == null) {
508 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR,
509 "Failed to parse json node str." + " Parse resulted a null value.");
510 ain.changeState(NodeProcessingState.ERROR,
511 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
515 Iterator<Entry<String, JsonNode>> fieldNames = jsonNode.fields();
516 Entry<String, JsonNode> field = null;
518 RelationshipList relationshipList = null;
520 while (fieldNames.hasNext()) {
522 field = fieldNames.next();
523 String fieldName = field.getKey();
525 if ("relationship-list".equals(fieldName)) {
528 relationshipList = mapper.readValue(field.getValue().toString(), RelationshipList.class);
530 if (relationshipList != null) {
531 ain.addRelationshipList(relationshipList);
534 } catch (Exception exc) {
535 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse relationship-list"
536 + " attribute. Parse resulted in error, " + exc.getLocalizedMessage());
537 ain.changeState(NodeProcessingState.ERROR,
538 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
544 JsonNode nodeValue = field.getValue();
546 if (nodeValue != null && nodeValue.isValueNode()) {
548 if (OxmEntityLookup.getInstance().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(fieldName, nodeValue.asText());
561 if (nodeValue.isArray()) {
563 if (OxmEntityLookup.getInstance().getEntityDescriptors().get(fieldName) == null) {
566 * entity property name is not an entity, thus we can add this property name and value
567 * to our property set
570 ain.addProperty(field.getKey(), nodeValue.toString());
576 ain.addComplexGroup(nodeValue);
585 String uri = NodeUtils.calculateEditAttributeUri(ain.getSelfLink());
587 ain.addProperty(TierSupportUiConstants.URI_ATTR_NAME, uri);
591 * We need a special behavior for intermediate entities from the REST model
593 * Tenants are not top level entities, and when we want to visualization their children, we need
594 * to construct keys that include the parent entity query keys, the current entity type keys,
595 * and the child keys. We'll always have the current entity and children, but never the parent
596 * entity in the current (1707) REST data model.
598 * We have two possible solutions:
600 * 1) Try to use the custom-query approach to learn about the entity keys - this could be done,
601 * but it could be very expensive for large objects. When we do the first query to get a tenant,
602 * it will list all the in and out edges related to this entity, there is presently no way to
603 * filter this. But the approach could be made to work and it would be somewhat data-model
604 * driven, other than the fact that we have to first realize that the entity that is being
605 * searched for is not top-level entity. Once we have globally unique ids for resources this
606 * logic will not be needed and everything will be simpler. The only reason we are in this logic
607 * at all is to be able to calculate a url for the child entities so we can hash it to generate
608 * a globally unique id that can be safely used for the node.
610 * *2* Extract the keys from the pathed self-link. This is a bad solution and I don't like it
611 * but it will be fast for all resource types, as the information is already encoded in the URI.
612 * When we get to a point where we switch to a better globally unique entity identity model,
613 * then a lot of the code being used to calculate an entity url to in-turn generate a
614 * deterministic globally unique id will disappear.
617 * right now we have the following:
619 * - cloud-regions/cloud-region/{cloud-region-id}/{cloud-owner-id}/tenants/tenant/{tenant-id}
624 * For all entity types use the self-link extraction method to be consistent. Once we have a
625 * globally unique identity mechanism for entities, this logic can be revisited.
627 ain.clearQueryParams();
628 ain.addQueryParams(extractQueryParamsFromSelfLink(ain.getSelfLink()));
629 ain.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
630 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
636 * Perform self link resolve.
638 * @param nodeId the node id
640 private void performSelfLinkResolve(String nodeId) {
642 if (nodeId == null) {
643 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
644 "Resolve of self-link" + " has been skipped because provided nodeId is null");
648 ActiveInventoryNode ain = nodeCache.get(nodeId);
651 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId
652 + ", from node cache. Resolve self-link method has been skipped.");
656 if (!ain.isSelfLinkPendingResolve()) {
658 ain.setSelfLinkPendingResolve(true);
660 // kick off async self-link resolution
662 if (LOG.isDebugEnabled()) {
663 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
664 "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink());
667 numLinksDiscovered.incrementAndGet();
669 String depthModifier = DEPTH_ALL_MODIFIER;
672 * If the current node is the search target, we want to see everything the node has to offer
673 * from the self-link and not filter it to a single node.
676 if (shallowEntities.contains(ain.getEntityType()) && !ain.isRootNode()) {
677 depthModifier = NODES_ONLY_MODIFIER;
680 NodeProcessingTransaction txn = new NodeProcessingTransaction();
681 txn.setProcessingNode(ain);
682 txn.setRequestParameters(depthModifier);
683 aaiWorkOnHand.incrementAndGet();
684 supplyAsync(new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiAdapter, aaiConfig),
685 aaiExecutorService).whenComplete((nodeTxn, error) -> {
686 aaiWorkOnHand.decrementAndGet();
690 * an error processing the self link should probably result in the node processing
691 * state shifting to ERROR
694 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
696 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
697 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
699 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
703 totalLinksRetrieved.incrementAndGet();
705 OperationResult opResult = nodeTxn.getOpResult();
707 if (opResult != null && opResult.wasSuccessful()) {
709 if (!opResult.wasSuccessful()) {
710 numFailedLinkResolve.incrementAndGet();
713 if (opResult.isFromCache()) {
714 numSuccessfulLinkResolveFromCache.incrementAndGet();
716 numSuccessfulLinkResolveFromFromServer.incrementAndGet();
720 nodeTxn.getProcessingNode().setOpResult(opResult);
721 nodeTxn.getProcessingNode().changeState(
722 NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
723 NodeProcessingAction.SELF_LINK_RESOLVE_OK);
725 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
726 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
729 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
730 "Self Link retrieval for link," + txn.getSelfLinkWithModifiers()
731 + ", failed with error code," + nodeTxn.getOpResult().getResultCode()
732 + ", and message," + nodeTxn.getOpResult().getResult());
734 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
735 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
737 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
738 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
740 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
755 * @param nodeId the node id
757 private void processNeighbors(String nodeId) {
759 if (nodeId == null) {
760 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR,
761 "Failed to process" + " neighbors because nodeId is null.");
765 ActiveInventoryNode ain = nodeCache.get(nodeId);
768 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
769 + " neighbors because node could not be found in nodeCache with id, " + nodeId);
774 * process complex attribute and relationships
777 boolean neighborsProcessedSuccessfully = true;
779 for (JsonNode n : ain.getComplexGroups()) {
780 neighborsProcessedSuccessfully &= decodeComplexAttributeGroup(ain, n);
783 for (RelationshipList relationshipList : ain.getRelationshipLists()) {
784 neighborsProcessedSuccessfully &= addSelfLinkRelationshipChildren(ain, relationshipList);
788 if (neighborsProcessedSuccessfully) {
789 ain.changeState(NodeProcessingState.READY, NodeProcessingAction.NEIGHBORS_PROCESSED_OK);
791 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
796 * If neighbors fail to process, there is already a call to change the state within the
797 * relationship and neighbor processing functions.
803 * Find and mark root node.
805 * @param queryParams the query params
806 * @return true, if successful
808 private boolean findAndMarkRootNode(QueryParams queryParams) {
810 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
812 if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
813 cacheNode.setNodeDepth(0);
814 cacheNode.setRootNode(true);
815 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
825 * Process current node states.
827 * @param rootNodeDiscovered the root node discovered
829 private void processCurrentNodeStates(boolean rootNodeDiscovered) {
831 * Force an evaluation of node depths before determining if we should limit state-based
832 * traversal or processing.
834 if (rootNodeDiscovered) {
835 evaluateNodeDepths();
838 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
840 if (LOG.isDebugEnabled()) {
841 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "processCurrentNodeState(), nid = "
842 + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
845 switch (cacheNode.getState()) {
848 processInitialState(cacheNode.getNodeId());
857 case SELF_LINK_UNRESOLVED: {
858 performSelfLinkResolve(cacheNode.getNodeId());
862 case SELF_LINK_RESPONSE_UNPROCESSED: {
863 processSelfLinkResponse(cacheNode.getNodeId());
867 case NEIGHBORS_UNPROCESSED: {
870 * We use the rootNodeDiscovered flag to ignore depth retrieval thresholds until the root
871 * node is identified. Then the evaluative depth calculations should re-balance the graph
872 * around the root node.
875 if (!rootNodeDiscovered || cacheNode.getNodeDepth() < this.visualizationConfigs
876 .getMaxSelfLinkTraversalDepth()) {
878 if (LOG.isDebugEnabled()) {
879 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
880 "SLNC::processCurrentNodeState() -- Node at max depth,"
881 + " halting processing at current state = -- " + cacheNode.getState()
882 + " nodeId = " + cacheNode.getNodeId());
887 processNeighbors(cacheNode.getNodeId());
905 * Adds the complex group to node.
907 * @param targetNode the target node
908 * @param attributeGroup the attribute group
909 * @return true, if successful
911 private boolean addComplexGroupToNode(ActiveInventoryNode targetNode, JsonNode attributeGroup) {
913 if (attributeGroup == null) {
914 targetNode.changeState(NodeProcessingState.ERROR,
915 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
919 RelationshipList relationshipList = null;
921 if (attributeGroup.isObject()) {
923 Iterator<Entry<String, JsonNode>> fields = attributeGroup.fields();
924 Entry<String, JsonNode> field = null;
928 while (fields.hasNext()) {
929 field = fields.next();
930 fieldName = field.getKey();
931 fieldValue = field.getValue();
933 if (fieldValue.isObject()) {
935 if (fieldName.equals("relationship-list")) {
939 mapper.readValue(field.getValue().toString(), RelationshipList.class);
941 if (relationshipList != null) {
942 targetNode.addRelationshipList(relationshipList);
945 } catch (Exception exc) {
946 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR,
947 "Failed to parse" + " relationship-list attribute. Parse resulted in error, "
948 + exc.getLocalizedMessage());
949 targetNode.changeState(NodeProcessingState.ERROR,
950 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_ERROR);
955 targetNode.addComplexGroup(fieldValue);
958 } else if (fieldValue.isArray()) {
959 if (LOG.isDebugEnabled()) {
960 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "Unexpected array type with a key = " + fieldName);
962 } else if (fieldValue.isValueNode()) {
963 if (OxmEntityLookup.getInstance().getEntityDescriptors().get(field.getKey()) == null) {
965 * property key is not an entity type, add it to our property set.
967 targetNode.addProperty(field.getKey(), fieldValue.asText());
973 } else if (attributeGroup.isArray()) {
974 if (LOG.isDebugEnabled()) {
975 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
976 "Unexpected array type for attributeGroup = " + attributeGroup);
978 } else if (attributeGroup.isValueNode()) {
979 if (LOG.isDebugEnabled()) {
980 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
981 "Unexpected value type for attributeGroup = " + attributeGroup);
988 public int getNumSuccessfulLinkResolveFromCache() {
989 return numSuccessfulLinkResolveFromCache.get();
992 public int getNumSuccessfulLinkResolveFromFromServer() {
993 return numSuccessfulLinkResolveFromFromServer.get();
996 public int getNumFailedLinkResolve() {
997 return numFailedLinkResolve.get();
1000 public InlineMessage getInlineMessage() {
1001 return inlineMessage;
1004 public void setInlineMessage(InlineMessage inlineMessage) {
1005 this.inlineMessage = inlineMessage;
1008 public void setMaxSelfLinkTraversalDepth(int depth) {
1009 this.maxSelfLinkTraversalDepth = depth;
1012 public int getMaxSelfLinkTraversalDepth() {
1013 return this.maxSelfLinkTraversalDepth;
1016 public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
1021 * Gets the relationship primary key values.
1024 * @param entityType the entity type
1025 * @param pkeyNames the pkey names
1026 * @return the relationship primary key values
1028 private String getRelationshipPrimaryKeyValues(Relationship r, String entityType,
1029 List<String> pkeyNames) {
1031 StringBuilder sb = new StringBuilder(64);
1033 if (pkeyNames.size() > 0) {
1034 String primaryKey = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(0));
1035 if (primaryKey != null) {
1037 sb.append(primaryKey);
1040 // this should be a fatal error because unless we can
1041 // successfully retrieve all the expected keys we'll end up
1042 // with a garbage node
1043 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract" + " keyName, " + entityType
1044 + "." + pkeyNames.get(0) + ", from relationship data, " + r.toString());
1048 for (int i = 1; i < pkeyNames.size(); i++) {
1050 String kv = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(i));
1052 sb.append("/").append(kv);
1054 // this should be a fatal error because unless we can
1055 // successfully retrieve all the expected keys we'll end up
1056 // with a garbage node
1057 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: failed to extract keyName, " + entityType
1058 + "." + pkeyNames.get(i) + ", from relationship data, " + r.toString());
1063 return sb.toString();
1072 * Extract key value from relation data.
1075 * @param keyName the key name
1076 * @return the string
1078 private String extractKeyValueFromRelationData(Relationship r, String keyName) {
1080 RelationshipData[] rdList = r.getRelationshipData();
1082 for (RelationshipData relData : rdList) {
1084 if (relData.getRelationshipKey().equals(keyName)) {
1085 return relData.getRelationshipValue();
1093 * Determine node id and key.
1095 * @param ain the ain
1096 * @return true, if successful
1098 private boolean addNodeQueryParams(ActiveInventoryNode ain) {
1101 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "ActiveInventoryNode is null");
1105 List<String> pkeyNames = OxmEntityLookup.getInstance().getEntityDescriptors()
1106 .get(ain.getEntityType()).getPrimaryKeyAttributeNames();
1108 if (pkeyNames == null || pkeyNames.size() == 0) {
1109 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Primary key names is empty");
1113 StringBuilder sb = new StringBuilder(64);
1115 if (pkeyNames.size() > 0) {
1116 String primaryKey = ain.getProperties().get(pkeyNames.get(0));
1117 if (primaryKey != null) {
1118 sb.append(primaryKey);
1120 // this should be a fatal error because unless we can
1121 // successfully retrieve all the expected keys we'll end up
1122 // with a garbage node
1123 LOG.error(AaiUiMsgs.EXTRACTION_ERROR,
1124 "ERROR: Failed to extract keyName, " + pkeyNames.get(0) + ", from entity properties");
1128 for (int i = 1; i < pkeyNames.size(); i++) {
1130 String kv = ain.getProperties().get(pkeyNames.get(i));
1132 sb.append("/").append(kv);
1134 // this should be a fatal error because unless we can
1135 // successfully retrieve all the expected keys we'll end up
1136 // with a garbage node
1137 LOG.error(AaiUiMsgs.EXTRACTION_ERROR,
1138 "ERROR: Failed to extract keyName, " + pkeyNames.get(i) + ", from entity properties");
1144 * final String nodeId = NodeUtils.generateUniqueShaDigest(ain.getEntityType(),
1145 * NodeUtils.concatArray(pkeyNames, "/"), sb.toString());
1148 // ain.setNodeId(nodeId);
1149 ain.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1150 ain.setPrimaryKeyValue(sb.toString());
1152 if (ain.getEntityType() != null && ain.getPrimaryKeyName() != null
1153 && ain.getPrimaryKeyValue() != null) {
1155 ain.getEntityType() + "." + ain.getPrimaryKeyName() + ":" + ain.getPrimaryKeyValue());
1166 * Adds the self link relationship children.
1168 * @param processingNode the processing node
1169 * @param relationshipList the relationship list
1170 * @return true, if successful
1172 private boolean addSelfLinkRelationshipChildren(ActiveInventoryNode processingNode,
1173 RelationshipList relationshipList) {
1175 if (relationshipList == null) {
1176 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "No relationships added to parent node = "
1177 + processingNode.getNodeId() + " because relationshipList is empty");
1178 processingNode.changeState(NodeProcessingState.ERROR,
1179 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1183 Relationship[] relationshipArray = relationshipList.getRelationshipList();
1184 OxmEntityDescriptor descriptor = null;
1185 String repairedSelfLink = null;
1187 if (relationshipArray != null) {
1189 ActiveInventoryNode newNode = null;
1190 String resourcePath = null;
1192 for (Relationship r : relationshipArray) {
1194 resourcePath = ActiveInventoryConfig.extractResourcePath(r.getRelatedLink());
1196 String nodeId = NodeUtils.generateUniqueShaDigest(resourcePath);
1198 if (nodeId == null) {
1200 LOG.error(AaiUiMsgs.SKIPPING_RELATIONSHIP, r.toString());
1201 processingNode.changeState(NodeProcessingState.ERROR,
1202 NodeProcessingAction.NODE_IDENTITY_ERROR);
1206 newNode = new ActiveInventoryNode(this.visualizationConfigs);
1208 String entityType = r.getRelatedTo();
1210 if (r.getRelationshipData() != null) {
1211 for (RelationshipData rd : r.getRelationshipData()) {
1212 newNode.addQueryParam(rd.getRelationshipKey() + ":" + rd.getRelationshipValue());
1216 descriptor = OxmEntityLookup.getInstance().getEntityDescriptors().get(r.getRelatedTo());
1218 newNode.setNodeId(nodeId);
1219 newNode.setEntityType(entityType);
1220 newNode.setSelfLink(resourcePath);
1222 processingNode.addOutboundNeighbor(nodeId);
1224 if (descriptor != null) {
1226 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1228 newNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1229 NodeProcessingAction.SELF_LINK_SET);
1231 newNode.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1233 String primaryKeyValues = getRelationshipPrimaryKeyValues(r, entityType, pkeyNames);
1234 newNode.setPrimaryKeyValue(primaryKeyValues);
1238 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR,
1239 "Failed to parse entity because OXM descriptor could not be found for type = "
1240 + r.getRelatedTo());
1242 newNode.changeState(NodeProcessingState.ERROR,
1243 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1247 if (nodeCache.putIfAbsent(nodeId, newNode) != null) {
1248 if (LOG.isDebugEnabled()) {
1249 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1250 "Failed to add node to nodeCache because it already exists. Node id = "
1251 + newNode.getNodeId());
1264 * Process initial state.
1266 * @param nodeId the node id
1268 private void processInitialState(String nodeId) {
1270 if (nodeId == null) {
1271 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
1275 ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
1277 if (cachedNode == null) {
1278 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE,
1279 "Node cannot be" + " found for nodeId, " + nodeId);
1283 if (cachedNode.getSelfLink() == null) {
1285 if (cachedNode.getNodeId() == null) {
1288 * if the self link is null at the INIT state, which could be valid if this node is a
1289 * complex attribute group which didn't originate from a self-link, but in that situation
1290 * both the node id and node key should already be set.
1293 cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
1297 if (cachedNode.getNodeId() != null) {
1300 * This should be the success path branch if the self-link is not set
1303 cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
1304 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
1310 if (cachedNode.hasResolvedSelfLink()) {
1311 LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
1312 cachedNode.changeState(NodeProcessingState.ERROR,
1313 NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
1315 cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1316 NodeProcessingAction.SELF_LINK_SET);
1322 * Process skeleton node.
1324 * @param skeletonNode the skeleton node
1325 * @param queryParams the query params
1327 private void processSearchableEntity(SearchableEntity searchTargetEntity,
1328 QueryParams queryParams) {
1330 if (searchTargetEntity == null) {
1334 if (searchTargetEntity.getId() == null) {
1335 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
1336 + " node because nodeId is null for node, " + searchTargetEntity.getLink());
1340 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs);
1342 newNode.setNodeId(searchTargetEntity.getId());
1343 newNode.setEntityType(searchTargetEntity.getEntityType());
1344 newNode.setPrimaryKeyName(getEntityTypePrimaryKeyName(searchTargetEntity.getEntityType()));
1345 newNode.setPrimaryKeyValue(searchTargetEntity.getEntityPrimaryKeyValue());
1347 if (newNode.getEntityType() != null && newNode.getPrimaryKeyName() != null
1348 && newNode.getPrimaryKeyValue() != null) {
1349 newNode.addQueryParam(newNode.getEntityType() + "." + newNode.getPrimaryKeyName() + ":"
1350 + newNode.getPrimaryKeyValue());
1353 * This code may need some explanation. In any graph there will be a single root node. The root
1354 * node is really the center of the universe, and for now, we are tagging the search target as
1355 * the root node. Everything else in the visualization of the graph will be centered around this
1356 * node as the focal point of interest.
1358 * Due to it's special nature, there will only ever be one root node, and it's node depth will
1359 * always be equal to zero.
1362 if (queryParams.getSearchTargetNodeId().equals(newNode.getNodeId())) {
1363 newNode.setNodeDepth(0);
1364 newNode.setRootNode(true);
1365 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
1368 newNode.setSelfLink(searchTargetEntity.getLink());
1370 nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
1374 * Checks for out standing work.
1376 * @return true, if successful
1378 private boolean hasOutStandingWork() {
1380 int numNodesWithPendingStates = 0;
1383 * Force an evaluation of node depths before determining if we should limit state-based
1384 * traversal or processing.
1387 evaluateNodeDepths();
1389 for (ActiveInventoryNode n : nodeCache.values()) {
1391 switch (n.getState()) {
1395 // do nothing, these are our normal
1400 case NEIGHBORS_UNPROCESSED: {
1402 if (n.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
1404 * Only process our neighbors relationships if our current depth is less than the max
1407 numNodesWithPendingStates++;
1416 * for all other states, there is work to be done
1418 numNodesWithPendingStates++;
1425 LOG.debug(AaiUiMsgs.OUTSTANDING_WORK_PENDING_NODES, String.valueOf(numNodesWithPendingStates));
1427 return (numNodesWithPendingStates > 0);
1432 * Process self links.
1434 * @param skeletonNode the skeleton node
1435 * @param queryParams the query params
1437 public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
1441 if (searchtargetEntity == null) {
1442 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
1443 contextIdStr + " - Failed to" + " processSelfLinks, searchtargetEntity is null");
1447 processSearchableEntity(searchtargetEntity, queryParams);
1449 long startTimeInMs = System.currentTimeMillis();
1452 * wait until all transactions are complete or guard-timer expires.
1455 long totalResolveTime = 0;
1456 boolean hasOutstandingWork = hasOutStandingWork();
1457 boolean outstandingWorkGuardTimerFired = false;
1458 long maxGuardTimeInMs = 5000;
1459 long guardTimeInMs = 0;
1460 boolean foundRootNode = false;
1464 * TODO: Put a count-down-latch in place of the while loop, but if we do that then we'll need
1465 * to decouple the visualization processing from the main thread so it can continue to process
1466 * while the main thread is waiting on for count-down-latch gate to open. This may also be
1467 * easier once we move to the VisualizationService + VisualizationContext ideas.
1471 while (hasOutstandingWork || !outstandingWorkGuardTimerFired) {
1473 if (!foundRootNode) {
1474 foundRootNode = findAndMarkRootNode(queryParams);
1477 processCurrentNodeStates(foundRootNode);
1479 verifyOutboundNeighbors();
1483 } catch (InterruptedException exc) {
1484 LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
1488 totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
1490 if (!hasOutstandingWork) {
1492 guardTimeInMs += 500;
1494 if (guardTimeInMs > maxGuardTimeInMs) {
1495 outstandingWorkGuardTimerFired = true;
1501 hasOutstandingWork = hasOutStandingWork();
1505 long opTime = System.currentTimeMillis() - startTimeInMs;
1507 LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
1508 String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
1510 } catch (Exception exc) {
1511 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
1517 * Verify outbound neighbors.
1519 private void verifyOutboundNeighbors() {
1521 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1523 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1525 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1527 if (targetNode != null && srcNode.getNodeId() != null) {
1529 targetNode.addInboundNeighbor(srcNode.getNodeId());
1531 if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
1532 targetNode.addOutboundNeighbor(srcNode.getNodeId());
1544 * Evaluate node depths.
1546 private void evaluateNodeDepths() {
1548 int numChanged = -1;
1549 int numAttempts = 0;
1551 while (numChanged != 0) {
1556 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1558 if (srcNode.getState() == NodeProcessingState.INIT) {
1561 * this maybe the only state that we don't want to to process the node depth on, because
1562 * typically it won't have any valid fields set, and it may remain in a partial state
1563 * until we have processed the self-link.
1570 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1571 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1573 if (targetNode != null) {
1575 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1581 for (String targetNodeId : srcNode.getInboundNeighbors()) {
1582 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1584 if (targetNode != null) {
1586 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1593 if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
1594 LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
1600 if (LOG.isDebugEnabled()) {
1601 if (numAttempts > 0) {
1602 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1603 "Evaluate node depths completed in " + numAttempts + " attempts");
1605 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1606 "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
1614 * Gets the entity type primary key name.
1616 * @param entityType the entity type
1617 * @return the entity type primary key name
1621 private String getEntityTypePrimaryKeyName(String entityType) {
1623 if (entityType == null) {
1624 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
1625 "node primary key" + " name because entity type is null");
1629 OxmEntityDescriptor descriptor =
1630 OxmEntityLookup.getInstance().getEntityDescriptors().get(entityType);
1632 if (descriptor == null) {
1633 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
1634 "oxm entity" + " descriptor for entityType = " + entityType);
1638 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1640 if (pkeyNames == null || pkeyNames.size() == 0) {
1641 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
1642 "node primary" + " key because descriptor primary key names is empty");
1646 return NodeUtils.concatArray(pkeyNames, "/");