2 * ============LICENSE_START===================================================
3 * SPARKY (AAI UI service)
4 * ============================================================================
5 * Copyright © 2017 AT&T Intellectual Property.
6 * Copyright © 2017 Amdocs
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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=====================================================
22 * ECOMP and OpenECOMP are trademarks
23 * and service marks of AT&T Intellectual Property.
25 package org.onap.aai.sparky.viewandinspect.services;
27 import static java.util.concurrent.CompletableFuture.supplyAsync;
29 import java.net.URISyntaxException;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Iterator;
33 import java.util.List;
35 import java.util.Map.Entry;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ExecutorService;
38 import java.util.concurrent.atomic.AtomicInteger;
40 import org.apache.http.client.utils.URIBuilder;
41 import org.onap.aai.cl.api.Logger;
42 import org.onap.aai.cl.eelf.LoggerFactory;
43 import org.onap.aai.restclient.client.OperationResult;
44 import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
45 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
46 import org.onap.aai.sparky.dal.ActiveInventoryAdapter;
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.SparkyConstants;
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 BaseVisualizationContext implements 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 = LoggerFactory.getInstance().getLogger(
81 BaseVisualizationContext.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 aaiWorkOnHand;
91 private VisualizationConfigs visualizationConfigs;
93 private AtomicInteger totalLinksRetrieved;
95 private final long contextId;
96 private final String contextIdStr;
98 private ObjectMapper mapper;
99 private InlineMessage inlineMessage = null;
101 private ExecutorService aaiExecutorService;
102 private OxmEntityLookup oxmEntityLookup;
103 private boolean rootNodeFound;
106 * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
107 * re-requesting the same self-links over-and-over again, to speed up the overall render time and
108 * more importantly to reduce the network cost of determining information we already have.
110 private ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
113 * Instantiates a new self link node collector.
115 * @param loader the loader
116 * @throws Exception the exception
118 public BaseVisualizationContext(long contextId, ActiveInventoryAdapter aaiAdapter,
119 ExecutorService aaiExecutorService, VisualizationConfigs visualizationConfigs,
120 OxmEntityLookup oxmEntityLookup)
123 this.contextId = contextId;
124 this.contextIdStr = "[Context-Id=" + contextId + "]";
125 this.aaiAdapter = aaiAdapter;
126 this.aaiExecutorService = aaiExecutorService;
127 this.visualizationConfigs = visualizationConfigs;
128 this.oxmEntityLookup = oxmEntityLookup;
130 this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
131 this.numLinksDiscovered = new AtomicInteger(0);
132 this.totalLinksRetrieved = new AtomicInteger(0);
133 this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
134 this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
135 this.numFailedLinkResolve = new AtomicInteger(0);
136 this.aaiWorkOnHand = new AtomicInteger(0);
138 this.maxSelfLinkTraversalDepth = this.visualizationConfigs.getMaxSelfLinkTraversalDepth();
140 this.mapper = new ObjectMapper();
141 mapper.setSerializationInclusion(Include.NON_EMPTY);
142 mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
143 this.rootNodeFound = false;
146 protected boolean isRootNodeFound() {
147 return rootNodeFound;
150 protected void setRootNodeFound(boolean rootNodeFound) {
151 this.rootNodeFound = rootNodeFound;
154 public long getContextId() {
159 * A utility method for extracting all entity-type primary key values from a provided self-link
160 * and return a set of generic-query API keys.
162 * @param parentEntityType
164 * @return a list of key values that can be used for this entity with the AAI generic-query API
166 protected List<String> extractQueryParamsFromSelfLink(String link) {
168 List<String> queryParams = new ArrayList<String>();
171 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, "self link is null");
175 Map<String, OxmEntityDescriptor> entityDescriptors = oxmEntityLookup.getEntityDescriptors();
179 URIBuilder urlBuilder = new URIBuilder(link);
180 String urlPath = urlBuilder.getPath();
182 OxmEntityDescriptor descriptor = null;
183 String[] urlPathElements = urlPath.split("/");
184 List<String> primaryKeyNames = null;
186 String entityType = null;
188 while (index < urlPathElements.length) {
190 descriptor = entityDescriptors.get(urlPathElements[index]);
192 if (descriptor != null) {
193 entityType = urlPathElements[index];
194 primaryKeyNames = descriptor.getPrimaryKeyAttributeNames();
197 * Make sure from what ever index we matched the parent entity-type on that we can extract
198 * additional path elements for the primary key values.
201 if (index + primaryKeyNames.size() < urlPathElements.length) {
203 for (String primaryKeyName : primaryKeyNames) {
205 queryParams.add(entityType + "." + primaryKeyName + ":" + urlPathElements[index]);
208 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
209 "Could not extract query parametrs for entity-type = '" + entityType
210 + "' from self-link = " + link);
217 } catch (URISyntaxException exc) {
219 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
220 "Error extracting query parameters from self-link = " + link + ". Error = "
229 * Decode complex attribute group.
232 * @param attributeGroup the attribute group
233 * @return boolean indicating whether operation was successful (true), / failure(false).
235 public boolean decodeComplexAttributeGroup(ActiveInventoryNode ain, JsonNode attributeGroup) {
239 Iterator<Entry<String, JsonNode>> entityArrays = attributeGroup.fields();
240 Entry<String, JsonNode> entityArray = null;
242 if (entityArrays == null) {
243 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, attributeGroup.toString());
244 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
248 while (entityArrays.hasNext()) {
250 entityArray = entityArrays.next();
252 String entityType = entityArray.getKey();
253 JsonNode entityArrayObject = entityArray.getValue();
255 if (entityArrayObject.isArray()) {
257 Iterator<JsonNode> entityCollection = entityArrayObject.elements();
258 JsonNode entity = null;
259 while (entityCollection.hasNext()) {
260 entity = entityCollection.next();
262 if (LOG.isDebugEnabled()) {
263 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "decodeComplexAttributeGroup(),"
264 + " entity = " + entity.toString());
268 * Here's what we are going to do:
270 * <li>In the ActiveInventoryNode, on construction maintain a collection of queryParams
271 * that is added to for the purpose of discovering parent->child hierarchies.
273 * <li>When we hit this block of the code then we'll use the queryParams to feed the
274 * generic query to resolve the self-link asynchronously.
276 * <li>Upon successful link determination, then and only then will we create a new node
277 * in the nodeCache and process the child
281 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
282 newNode.setEntityType(entityType);
285 * This is partially a lie because we actually don't have a self-link for complex nodes
286 * discovered in this way.
288 newNode.setSelfLinkProcessed(true);
289 newNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
290 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
293 * copy parent query params into new child
296 if (SERVICE_INSTANCE.equals(entityType)) {
299 * 1707 AAI has an issue being tracked with AAI-8932 where the generic-query cannot be
300 * resolved if all the service-instance path keys are provided. The query only works
301 * if only the service-instance key and valude are passed due to a historical reason.
302 * A fix is being worked on for 1707, and when it becomes available we can revert this
306 newNode.clearQueryParams();
311 * For all other entity-types we want to copy the parent query parameters into the new node
315 for (String queryParam : ain.getQueryParams()) {
316 newNode.addQueryParam(queryParam);
322 if (!addComplexGroupToNode(newNode, entity)) {
323 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, "Failed to add child to parent for child = " + entity.toString());
326 if (!addNodeQueryParams(newNode)) {
327 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "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) -> {
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(), exc.getMessage());
400 NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link",
403 String selfLink = null;
405 if (entityLinks.size() != 1) {
407 LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS, String.valueOf(entityLinks.size()));
410 selfLink = ((JsonNode) entityLinks.toArray()[0]).asText();
411 selfLink = ActiveInventoryAdapter.extractResourcePath(selfLink);
413 newChildNode.setSelfLink(selfLink);
414 newChildNode.setNodeId(NodeUtils.generateUniqueShaDigest(selfLink));
416 String uri = NodeUtils.calculateEditAttributeUri(selfLink);
418 newChildNode.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
421 ActiveInventoryNode parent = nodeCache.get(txn.getParentNodeId());
423 if (parent != null) {
424 parent.addOutboundNeighbor(newChildNode.getNodeId());
425 newChildNode.addInboundNeighbor(parent.getNodeId());
428 newChildNode.setSelfLinkPendingResolve(false);
429 newChildNode.setSelfLinkProcessed(true);
430 newChildNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
431 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
433 nodeCache.putIfAbsent(newChildNode.getNodeId(), newChildNode);
438 LOG.error(AaiUiMsgs.SELF_LINK_RETRIEVAL_FAILED, txn.getQueryString(),
439 String.valueOf(nodeTxn.getOpResult().getResultCode()), nodeTxn.getOpResult().getResult());
440 newChildNode.setSelflinkRetrievalFailure(true);
441 newChildNode.setSelfLinkProcessed(true);
442 newChildNode.setSelfLinkPendingResolve(false);
444 newChildNode.changeState(NodeProcessingState.ERROR,
445 NodeProcessingAction.SELF_LINK_DETERMINATION_ERROR);
451 aaiWorkOnHand.decrementAndGet();
460 LOG.error(AaiUiMsgs.UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE, entityType);
464 } catch (Exception exc) {
465 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Exception caught while"
466 + " decoding complex attribute group - " + exc.getMessage());
474 * Process self link response.
476 * @param nodeId the node id
478 private void processSelfLinkResponse(String nodeId) {
480 if (nodeId == null) {
481 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link"
482 + " response because nodeId is null");
486 ActiveInventoryNode ain = nodeCache.get(nodeId);
489 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link response"
490 + " because can't find node for id = " + nodeId);
494 JsonNode jsonNode = null;
497 jsonNode = mapper.readValue(ain.getOpResult().getResult(), JsonNode.class);
498 } catch (Exception exc) {
499 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
500 + " response str into JsonNode with error, " + exc.getLocalizedMessage());
501 ain.changeState(NodeProcessingState.ERROR,
502 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
506 if (jsonNode == null) {
507 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse json node str."
508 + " Parse resulted a null value.");
509 ain.changeState(NodeProcessingState.ERROR,
510 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
514 Iterator<Entry<String, JsonNode>> fieldNames = jsonNode.fields();
515 Entry<String, JsonNode> field = null;
517 RelationshipList relationshipList = null;
519 while (fieldNames.hasNext()) {
521 field = fieldNames.next();
522 String fieldName = field.getKey();
524 if ("relationship-list".equals(fieldName)) {
527 relationshipList = mapper.readValue(field.getValue().toString(), RelationshipList.class);
529 if (relationshipList != null) {
530 ain.addRelationshipList(relationshipList);
533 } catch (Exception exc) {
534 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse relationship-list"
535 + " attribute. Parse resulted in error, " + exc.getLocalizedMessage());
536 ain.changeState(NodeProcessingState.ERROR,
537 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
543 JsonNode nodeValue = field.getValue();
545 if (nodeValue != null && nodeValue.isValueNode()) {
547 if (oxmEntityLookup.getEntityDescriptors().get(fieldName) == null) {
550 * entity property name is not an entity, thus we can add this property name and value
551 * to our property set
554 ain.addProperty(fieldName, nodeValue.asText());
560 if (nodeValue.isArray()) {
562 if (oxmEntityLookup.getEntityDescriptors().get(fieldName) == null) {
565 * entity property name is not an entity, thus we can add this property name and value
566 * to our property set
569 ain.addProperty(field.getKey(), nodeValue.toString());
575 ain.addComplexGroup(nodeValue);
584 String uri = NodeUtils.calculateEditAttributeUri(ain.getSelfLink());
586 ain.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
590 * We need a special behavior for intermediate entities from the REST model
592 * Tenants are not top level entities, and when we want to visualization
593 * their children, we need to construct keys that include the parent entity query
594 * keys, the current entity type keys, and the child keys. We'll always have the
595 * current entity and children, but never the parent entity in the current (1707) REST
598 * We have two possible solutions:
600 * 1) Try to use the custom-query approach to learn about the entity keys
601 * - this could be done, but it could be very expensive for large objects. When we do the first
602 * query to get a tenant, it will list all the in and out edges related to this entity,
603 * there is presently no way to filter this. But the approach could be made to work and it would be
604 * somewhat data-model driven, other than the fact that we have to first realize that the entity
605 * that is being searched for is not top-level entity. Once we have globally unique ids for resources
606 * this 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.
611 * This is a bad solution and I don't like it but it will be fast for all resource types, as the
612 * information is already encoded in the URI. When we get to a point where we switch to a better
613 * globally unique entity identity model, then a lot of the code being used to calculate an entity url
614 * to in-turn generate a 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, "Resolve of self-link"
644 + " 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 (visualizationConfigs.getShallowEntities().contains(ain.getEntityType())
677 && !ain.isRootNode()) {
678 depthModifier = NODES_ONLY_MODIFIER;
681 NodeProcessingTransaction txn = new NodeProcessingTransaction();
682 txn.setProcessingNode(ain);
683 txn.setRequestParameters(depthModifier);
684 aaiWorkOnHand.incrementAndGet();
686 new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiAdapter),
687 aaiExecutorService).whenComplete((nodeTxn, error) -> {
692 * an error processing the self link should probably result in the node processing
693 * state shifting to ERROR
696 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
698 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
699 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
701 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
705 totalLinksRetrieved.incrementAndGet();
707 OperationResult opResult = nodeTxn.getOpResult();
709 if (opResult != null && opResult.wasSuccessful()) {
711 if (!opResult.wasSuccessful()) {
712 numFailedLinkResolve.incrementAndGet();
715 if (opResult.isFromCache()) {
716 numSuccessfulLinkResolveFromCache.incrementAndGet();
718 numSuccessfulLinkResolveFromFromServer.incrementAndGet();
722 nodeTxn.getProcessingNode().setOpResult(opResult);
723 nodeTxn.getProcessingNode().changeState(
724 NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
725 NodeProcessingAction.SELF_LINK_RESOLVE_OK);
727 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
728 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
731 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Self Link retrieval for link,"
732 + txn.getSelfLinkWithModifiers() + ", failed with error code,"
733 + nodeTxn.getOpResult().getResultCode() + ", and message,"
734 + nodeTxn.getOpResult().getResult());
736 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
737 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
739 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
740 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
742 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
747 aaiWorkOnHand.decrementAndGet();
759 * @param nodeId the node id
761 private void processNeighbors(String nodeId) {
763 if (nodeId == null) {
764 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
765 + " neighbors because nodeId is null.");
769 ActiveInventoryNode ain = nodeCache.get(nodeId);
772 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
773 + " neighbors because node could not be found in nodeCache with id, " + nodeId);
778 * process complex attribute and relationships
781 boolean neighborsProcessedSuccessfully = true;
783 for (JsonNode n : ain.getComplexGroups()) {
784 neighborsProcessedSuccessfully &= decodeComplexAttributeGroup(ain, n);
787 for (RelationshipList relationshipList : ain.getRelationshipLists()) {
788 neighborsProcessedSuccessfully &= addSelfLinkRelationshipChildren(ain, relationshipList);
792 if (neighborsProcessedSuccessfully) {
793 ain.changeState(NodeProcessingState.READY, NodeProcessingAction.NEIGHBORS_PROCESSED_OK);
795 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
800 * If neighbors fail to process, there is already a call to change the state within the
801 * relationship and neighbor processing functions.
807 * Find and mark root node.
809 * @param queryParams the query params
810 * @return true, if successful
812 private void findAndMarkRootNode(QueryParams queryParams) {
814 if (isRootNodeFound()) {
818 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
820 if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
821 cacheNode.setNodeDepth(0);
822 cacheNode.setRootNode(true);
823 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
824 setRootNodeFound(true);
831 * Process current node states.
833 * @param rootNodeDiscovered the root node discovered
835 private void processCurrentNodeStates(QueryParams queryParams) {
837 * Force an evaluation of node depths before determining if we should limit state-based
838 * traversal or processing.
841 findAndMarkRootNode(queryParams);
843 verifyOutboundNeighbors();
845 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
847 if (LOG.isDebugEnabled()) {
848 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
849 "processCurrentNodeState(), nid = "
850 + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
853 switch (cacheNode.getState()) {
856 processInitialState(cacheNode.getNodeId());
865 case SELF_LINK_UNRESOLVED: {
866 performSelfLinkResolve(cacheNode.getNodeId());
870 case SELF_LINK_RESPONSE_UNPROCESSED: {
871 processSelfLinkResponse(cacheNode.getNodeId());
875 case NEIGHBORS_UNPROCESSED: {
878 * We use the rootNodeDiscovered flag to ignore depth retrieval thresholds until the root
879 * node is identified. Then the evaluative depth calculations should re-balance the graph
880 * around the root node.
883 if (!isRootNodeFound() || cacheNode.getNodeDepth() < this.visualizationConfigs
884 .getMaxSelfLinkTraversalDepth()) {
886 if (LOG.isDebugEnabled()) {
887 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
888 "processCurrentNodeState() -- Node at max depth,"
889 + " halting processing at current state = -- "
890 + cacheNode.getState() + " nodeId = " + cacheNode.getNodeId());
893 processNeighbors(cacheNode.getNodeId());
908 * Adds the complex group to node.
910 * @param targetNode the target node
911 * @param attributeGroup the attribute group
912 * @return true, if successful
914 private boolean addComplexGroupToNode(ActiveInventoryNode targetNode, JsonNode attributeGroup) {
916 if (attributeGroup == null) {
917 targetNode.changeState(NodeProcessingState.ERROR,
918 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
922 RelationshipList relationshipList = null;
924 if (attributeGroup.isObject()) {
926 Iterator<Entry<String, JsonNode>> fields = attributeGroup.fields();
927 Entry<String, JsonNode> field = null;
931 while (fields.hasNext()) {
932 field = fields.next();
933 fieldName = field.getKey();
934 fieldValue = field.getValue();
936 if (fieldValue.isObject()) {
938 if (fieldName.equals("relationship-list")) {
942 mapper.readValue(field.getValue().toString(), RelationshipList.class);
944 if (relationshipList != null) {
945 targetNode.addRelationshipList(relationshipList);
948 } catch (Exception exc) {
949 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse"
950 + " relationship-list attribute. Parse resulted in error, "
951 + exc.getLocalizedMessage());
952 targetNode.changeState(NodeProcessingState.ERROR,
953 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_ERROR);
958 targetNode.addComplexGroup(fieldValue);
961 } else if (fieldValue.isArray()) {
962 if (LOG.isDebugEnabled()) {
963 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
964 "Unexpected array type with a key = " + fieldName);
966 } else if (fieldValue.isValueNode()) {
967 if (oxmEntityLookup.getEntityDescriptors().get(field.getKey()) == null) {
969 * property key is not an entity type, add it to our property set.
971 targetNode.addProperty(field.getKey(), fieldValue.asText());
977 } else if (attributeGroup.isArray()) {
978 if (LOG.isDebugEnabled()) {
979 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
980 "Unexpected array type for attributeGroup = " + attributeGroup);
982 } else if (attributeGroup.isValueNode()) {
983 if (LOG.isDebugEnabled()) {
984 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
985 "Unexpected value type for attributeGroup = " + attributeGroup);
992 public int getNumSuccessfulLinkResolveFromCache() {
993 return numSuccessfulLinkResolveFromCache.get();
996 public int getNumSuccessfulLinkResolveFromFromServer() {
997 return numSuccessfulLinkResolveFromFromServer.get();
1000 public int getNumFailedLinkResolve() {
1001 return numFailedLinkResolve.get();
1004 public InlineMessage getInlineMessage() {
1005 return inlineMessage;
1008 public void setInlineMessage(InlineMessage inlineMessage) {
1009 this.inlineMessage = inlineMessage;
1012 public void setMaxSelfLinkTraversalDepth(int depth) {
1013 this.maxSelfLinkTraversalDepth = depth;
1016 public int getMaxSelfLinkTraversalDepth() {
1017 return this.maxSelfLinkTraversalDepth;
1020 public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
1025 * Gets the relationship primary key values.
1028 * @param entityType the entity type
1029 * @param pkeyNames the pkey names
1030 * @return the relationship primary key values
1032 private String getRelationshipPrimaryKeyValues(Relationship r, String entityType,
1033 List<String> pkeyNames) {
1035 StringBuilder sb = new StringBuilder(64);
1037 if (pkeyNames.size() > 0) {
1038 String primaryKey = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(0));
1039 if (primaryKey != null) {
1041 sb.append(primaryKey);
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"
1048 + " keyName, " + entityType + "." + pkeyNames.get(0)
1049 + ", from relationship data, " + r.toString());
1053 for (int i = 1; i < pkeyNames.size(); i++) {
1055 String kv = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(i));
1057 sb.append("/").append(kv);
1059 // this should be a fatal error because unless we can
1060 // successfully retrieve all the expected keys we'll end up
1061 // with a garbage node
1062 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: failed to extract keyName, "
1063 + entityType + "." + pkeyNames.get(i)
1064 + ", from relationship data, " + r.toString());
1069 return sb.toString();
1078 * Extract key value from relation data.
1081 * @param keyName the key name
1082 * @return the string
1084 private String extractKeyValueFromRelationData(Relationship r, String keyName) {
1086 RelationshipData[] rdList = r.getRelationshipData();
1088 for (RelationshipData relData : rdList) {
1090 if (relData.getRelationshipKey().equals(keyName)) {
1091 return relData.getRelationshipValue();
1099 * Determine node id and key.
1101 * @param ain the ain
1102 * @return true, if successful
1104 private boolean addNodeQueryParams(ActiveInventoryNode ain) {
1107 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "ActiveInventoryNode is null");
1111 List<String> pkeyNames =
1112 oxmEntityLookup.getEntityDescriptors().get(ain.getEntityType()).getPrimaryKeyAttributeNames();
1114 if (pkeyNames == null || pkeyNames.size() == 0) {
1115 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Primary key names is empty");
1119 StringBuilder sb = new StringBuilder(64);
1121 if (pkeyNames.size() > 0) {
1122 String primaryKey = ain.getProperties().get(pkeyNames.get(0));
1123 if (primaryKey != null) {
1124 sb.append(primaryKey);
1126 // this should be a fatal error because unless we can
1127 // successfully retrieve all the expected keys we'll end up
1128 // with a garbage node
1129 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
1130 + pkeyNames.get(0) + ", from entity properties");
1134 for (int i = 1; i < pkeyNames.size(); i++) {
1136 String kv = ain.getProperties().get(pkeyNames.get(i));
1138 sb.append("/").append(kv);
1140 // this should be a fatal error because unless we can
1141 // successfully retrieve all the expected keys we'll end up
1142 // with a garbage node
1143 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
1144 + pkeyNames.get(i) + ", from entity properties");
1149 /*final String nodeId = NodeUtils.generateUniqueShaDigest(ain.getEntityType(),
1150 NodeUtils.concatArray(pkeyNames, "/"), sb.toString());*/
1152 //ain.setNodeId(nodeId);
1153 ain.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1154 ain.setPrimaryKeyValue(sb.toString());
1156 if (ain.getEntityType() != null && ain.getPrimaryKeyName() != null
1157 && ain.getPrimaryKeyValue() != null) {
1159 ain.getEntityType() + "." + ain.getPrimaryKeyName() + ":" + ain.getPrimaryKeyValue());
1170 * Adds the self link relationship children.
1172 * @param processingNode the processing node
1173 * @param relationshipList the relationship list
1174 * @return true, if successful
1176 private boolean addSelfLinkRelationshipChildren(ActiveInventoryNode processingNode,
1177 RelationshipList relationshipList) {
1179 if (relationshipList == null) {
1180 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "No relationships added to parent node = "
1181 + processingNode.getNodeId() + " because relationshipList is empty");
1182 processingNode.changeState(NodeProcessingState.ERROR,
1183 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1187 Relationship[] relationshipArray = relationshipList.getRelationshipList();
1188 OxmEntityDescriptor descriptor = null;
1189 String repairedSelfLink = null;
1191 if (relationshipArray != null) {
1193 ActiveInventoryNode newNode = null;
1194 String resourcePath = null;
1196 for (Relationship r : relationshipArray) {
1198 resourcePath = ActiveInventoryAdapter.extractResourcePath(r.getRelatedLink());
1200 String nodeId = NodeUtils.generateUniqueShaDigest(resourcePath);
1202 if (nodeId == null) {
1204 LOG.error(AaiUiMsgs.SKIPPING_RELATIONSHIP, r.toString());
1205 processingNode.changeState(NodeProcessingState.ERROR,
1206 NodeProcessingAction.NODE_IDENTITY_ERROR);
1210 newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
1212 String entityType = r.getRelatedTo();
1214 if (r.getRelationshipData() != null) {
1215 for (RelationshipData rd : r.getRelationshipData()) {
1216 newNode.addQueryParam(rd.getRelationshipKey() + ":" + rd.getRelationshipValue());
1220 descriptor = oxmEntityLookup.getEntityDescriptors().get(r.getRelatedTo());
1222 newNode.setNodeId(nodeId);
1223 newNode.setEntityType(entityType);
1224 newNode.setSelfLink(resourcePath);
1226 processingNode.addOutboundNeighbor(nodeId);
1228 if (descriptor != null) {
1230 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1232 newNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1233 NodeProcessingAction.SELF_LINK_SET);
1235 newNode.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1237 String primaryKeyValues = getRelationshipPrimaryKeyValues(r, entityType, pkeyNames);
1238 newNode.setPrimaryKeyValue(primaryKeyValues);
1242 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR,
1243 "Failed to parse entity because OXM descriptor could not be found for type = "
1244 + r.getRelatedTo());
1246 newNode.changeState(NodeProcessingState.ERROR,
1247 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1251 if (nodeCache.putIfAbsent(nodeId, newNode) != null) {
1252 if (LOG.isDebugEnabled()) {
1253 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1254 "Failed to add node to nodeCache because it already exists. Node id = "
1255 + newNode.getNodeId());
1268 * Process initial state.
1270 * @param nodeId the node id
1272 private void processInitialState(String nodeId) {
1274 if (nodeId == null) {
1275 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
1279 ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
1281 if (cachedNode == null) {
1282 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node cannot be"
1283 + " found for nodeId, " + nodeId);
1287 if (cachedNode.getSelfLink() == null) {
1289 if (cachedNode.getNodeId() == null ) {
1292 * if the self link is null at the INIT state, which could be valid if this node is a
1293 * complex attribute group which didn't originate from a self-link, but in that situation
1294 * both the node id and node key should already be set.
1297 cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
1301 if (cachedNode.getNodeId() != null) {
1304 * This should be the success path branch if the self-link is not set
1307 cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
1308 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
1314 if (cachedNode.hasResolvedSelfLink()) {
1315 LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
1316 cachedNode.changeState(NodeProcessingState.ERROR,
1317 NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
1319 cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1320 NodeProcessingAction.SELF_LINK_SET);
1326 * Process skeleton node.
1328 * @param skeletonNode the skeleton node
1329 * @param queryParams the query params
1331 private void processSearchableEntity(SearchableEntity searchTargetEntity, QueryParams queryParams) {
1333 if (searchTargetEntity == null) {
1337 if (searchTargetEntity.getId() == null) {
1338 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
1339 + " node because nodeId is null for node, " + searchTargetEntity.getLink());
1343 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
1345 newNode.setNodeId(searchTargetEntity.getId());
1346 newNode.setEntityType(searchTargetEntity.getEntityType());
1347 newNode.setPrimaryKeyName(getEntityTypePrimaryKeyName(searchTargetEntity.getEntityType()));
1348 newNode.setPrimaryKeyValue(searchTargetEntity.getEntityPrimaryKeyValue());
1350 if (newNode.getEntityType() != null && newNode.getPrimaryKeyName() != null
1351 && newNode.getPrimaryKeyValue() != null) {
1352 newNode.addQueryParam(
1353 newNode.getEntityType() + "." + newNode.getPrimaryKeyName() + ":" + newNode.getPrimaryKeyValue());
1356 * This code may need some explanation. In any graph there will be a single root node. The root
1357 * node is really the center of the universe, and for now, we are tagging the search target as
1358 * the root node. Everything else in the visualization of the graph will be centered around this
1359 * node as the focal point of interest.
1361 * Due to it's special nature, there will only ever be one root node, and it's node depth will
1362 * always be equal to zero.
1365 if (!isRootNodeFound()) {
1366 if (queryParams.getSearchTargetNodeId().equals(newNode.getNodeId())) {
1367 newNode.setNodeDepth(0);
1368 newNode.setRootNode(true);
1369 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
1370 setRootNodeFound(true);
1374 newNode.setSelfLink(searchTargetEntity.getLink());
1376 nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
1379 private int getTotalWorkOnHand() {
1381 int numNodesWithPendingStates = 0;
1383 if( isRootNodeFound()) {
1384 evaluateNodeDepths();
1387 for (ActiveInventoryNode n : nodeCache.values()) {
1389 switch (n.getState()) {
1393 // do nothing, these are our normal
1398 case NEIGHBORS_UNPROCESSED: {
1400 if (n.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
1402 * Only process our neighbors relationships if our current depth is less than the max
1405 numNodesWithPendingStates++;
1414 * for all other states, there is work to be done
1416 numNodesWithPendingStates++;
1423 LOG.debug(AaiUiMsgs.OUTSTANDING_WORK_PENDING_NODES,
1424 String.valueOf(numNodesWithPendingStates));
1426 int totalWorkOnHand = aaiWorkOnHand.get() + numNodesWithPendingStates;
1428 return totalWorkOnHand;
1433 * Checks for out standing work.
1435 * @return true, if successful
1437 private void processOutstandingWork(QueryParams queryParams) {
1439 while (getTotalWorkOnHand() > 0) {
1442 * Force an evaluation of node depths before determining if we should limit state-based
1443 * traversal or processing.
1446 processCurrentNodeStates(queryParams);
1450 } catch (InterruptedException exc) {
1451 LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
1460 * @see org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai.sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams)
1463 public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
1468 if (searchtargetEntity == null) {
1469 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, contextIdStr + " - Failed to"
1470 + " processSelfLinks, searchtargetEntity is null");
1474 long startTimeInMs = System.currentTimeMillis();
1476 processSearchableEntity(searchtargetEntity, queryParams);
1479 * This method is blocking until we decouple it with a CountDownLatch await condition,
1480 * and make the internal graph processing more event-y.
1483 processOutstandingWork(queryParams);
1485 long totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
1487 long opTime = System.currentTimeMillis() - startTimeInMs;
1489 LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
1490 String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
1492 } catch (Exception exc) {
1493 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
1499 * Verify outbound neighbors.
1501 private void verifyOutboundNeighbors() {
1503 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1505 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1507 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1509 if (targetNode != null && srcNode.getNodeId() != null) {
1511 targetNode.addInboundNeighbor(srcNode.getNodeId());
1513 if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
1514 targetNode.addOutboundNeighbor(srcNode.getNodeId());
1526 * Evaluate node depths.
1528 private void evaluateNodeDepths() {
1530 int numChanged = -1;
1531 int numAttempts = 0;
1533 while (numChanged != 0) {
1538 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1540 if (srcNode.getState() == NodeProcessingState.INIT) {
1543 * this maybe the only state that we don't want to to process the node depth on, because
1544 * typically it won't have any valid fields set, and it may remain in a partial state
1545 * until we have processed the self-link.
1552 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1553 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1555 if (targetNode != null) {
1557 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1563 for (String targetNodeId : srcNode.getInboundNeighbors()) {
1564 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1566 if (targetNode != null) {
1568 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1575 if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
1576 LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
1582 if (LOG.isDebugEnabled()) {
1583 if (numAttempts > 0) {
1584 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1585 "Evaluate node depths completed in " + numAttempts + " attempts");
1587 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1588 "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
1596 * Gets the entity type primary key name.
1598 * @param entityType the entity type
1599 * @return the entity type primary key name
1603 private String getEntityTypePrimaryKeyName(String entityType) {
1605 if (entityType == null) {
1606 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary key"
1607 + " name because entity type is null");
1611 OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
1613 if (descriptor == null) {
1614 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "oxm entity"
1615 + " descriptor for entityType = " + entityType);
1619 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1621 if (pkeyNames == null || pkeyNames.size() == 0) {
1622 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary"
1623 + " key because descriptor primary key names is empty");
1627 return NodeUtils.concatArray(pkeyNames, "/");