2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * Copyright © 2017-2018 Amdocs
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
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 package org.onap.aai.sparky.viewandinspect.context;
23 import static java.util.concurrent.CompletableFuture.supplyAsync;
25 import java.net.URISyntaxException;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Iterator;
29 import java.util.List;
31 import java.util.Map.Entry;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.atomic.AtomicInteger;
36 import org.apache.http.client.utils.URIBuilder;
37 import org.onap.aai.cl.api.Logger;
38 import org.onap.aai.cl.eelf.LoggerFactory;
39 import org.onap.aai.restclient.client.OperationResult;
40 import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
41 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
42 import org.onap.aai.sparky.dal.ActiveInventoryAdapter;
43 import org.onap.aai.sparky.logging.AaiUiMsgs;
44 import org.onap.aai.sparky.sync.entity.SearchableEntity;
45 import org.onap.aai.sparky.util.NodeUtils;
46 import org.onap.aai.sparky.viewandinspect.VisualizationContext;
47 import org.onap.aai.sparky.viewandinspect.config.SparkyConstants;
48 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
49 import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
50 import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction;
51 import org.onap.aai.sparky.viewandinspect.entity.QueryParams;
52 import org.onap.aai.sparky.viewandinspect.entity.Relationship;
53 import org.onap.aai.sparky.viewandinspect.entity.RelationshipData;
54 import org.onap.aai.sparky.viewandinspect.entity.RelationshipList;
55 import org.onap.aai.sparky.viewandinspect.entity.SelfLinkDeterminationTransaction;
56 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction;
57 import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState;
58 import org.onap.aai.sparky.viewandinspect.task.PerformNodeSelfLinkProcessingTask;
59 import org.onap.aai.sparky.viewandinspect.task.PerformSelfLinkDeterminationTask;
61 import com.fasterxml.jackson.annotation.JsonInclude.Include;
62 import com.fasterxml.jackson.databind.JsonNode;
63 import com.fasterxml.jackson.databind.ObjectMapper;
64 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
67 * The Class SelfLinkNodeCollector.
69 public class BaseVisualizationContext implements VisualizationContext {
71 protected static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100;
72 protected static final String DEPTH_ALL_MODIFIER = "?depth=all";
73 protected static final String NODES_ONLY_MODIFIER = "?nodes-only";
74 protected static final String SERVICE_INSTANCE = "service-instance";
76 private static final Logger LOG = LoggerFactory.getInstance().getLogger(
77 BaseVisualizationContext.class);
78 protected final ActiveInventoryAdapter aaiAdapter;
80 protected int maxSelfLinkTraversalDepth;
81 protected AtomicInteger numLinksDiscovered;
82 protected AtomicInteger numSuccessfulLinkResolveFromCache;
83 protected AtomicInteger numSuccessfulLinkResolveFromFromServer;
84 protected AtomicInteger numFailedLinkResolve;
85 protected AtomicInteger aaiWorkOnHand;
87 protected VisualizationConfigs visualizationConfigs;
89 protected AtomicInteger totalLinksRetrieved;
91 protected final long contextId;
92 protected final String contextIdStr;
94 protected ObjectMapper mapper;
96 protected ExecutorService aaiExecutorService;
97 protected OxmEntityLookup oxmEntityLookup;
98 protected boolean rootNodeFound;
101 * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
102 * re-requesting the same self-links over-and-over again, to speed up the overall render time and
103 * more importantly to reduce the network cost of determining information we already have.
105 protected ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
108 * Instantiates a new self link node collector.
110 * @param loader the loader
111 * @throws Exception the exception
113 public BaseVisualizationContext(long contextId, ActiveInventoryAdapter aaiAdapter,
114 ExecutorService aaiExecutorService, VisualizationConfigs visualizationConfigs,
115 OxmEntityLookup oxmEntityLookup)
118 this.contextId = contextId;
119 this.contextIdStr = "[Context-Id=" + contextId + "]";
120 this.aaiAdapter = aaiAdapter;
121 this.aaiExecutorService = aaiExecutorService;
122 this.visualizationConfigs = visualizationConfigs;
123 this.oxmEntityLookup = oxmEntityLookup;
125 this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
126 this.numLinksDiscovered = new AtomicInteger(0);
127 this.totalLinksRetrieved = new AtomicInteger(0);
128 this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
129 this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
130 this.numFailedLinkResolve = new AtomicInteger(0);
131 this.aaiWorkOnHand = new AtomicInteger(0);
133 this.maxSelfLinkTraversalDepth = this.visualizationConfigs.getMaxSelfLinkTraversalDepth();
135 this.mapper = new ObjectMapper();
136 mapper.setSerializationInclusion(Include.NON_EMPTY);
137 mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
138 this.rootNodeFound = false;
141 protected boolean isRootNodeFound() {
142 return rootNodeFound;
145 protected void setRootNodeFound(boolean rootNodeFound) {
146 this.rootNodeFound = rootNodeFound;
149 public long getContextId() {
154 * A utility method for extracting all entity-type primary key values from a provided self-link
155 * and return a set of generic-query API keys.
157 * @param parentEntityType
159 * @return a list of key values that can be used for this entity with the AAI generic-query API
161 protected List<String> extractQueryParamsFromSelfLink(String link) {
163 List<String> queryParams = new ArrayList<String>();
166 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, "self link is null");
170 Map<String, OxmEntityDescriptor> entityDescriptors = oxmEntityLookup.getEntityDescriptors();
174 URIBuilder urlBuilder = new URIBuilder(link);
175 String urlPath = urlBuilder.getPath();
177 OxmEntityDescriptor descriptor = null;
178 String[] urlPathElements = urlPath.split("/");
179 List<String> primaryKeyNames = null;
181 String entityType = null;
183 while (index < urlPathElements.length) {
185 descriptor = entityDescriptors.get(urlPathElements[index]);
187 if (descriptor != null) {
188 entityType = urlPathElements[index];
189 primaryKeyNames = descriptor.getPrimaryKeyAttributeNames();
192 * Make sure from what ever index we matched the parent entity-type on that we can extract
193 * additional path elements for the primary key values.
196 if (index + primaryKeyNames.size() < urlPathElements.length) {
198 for (String primaryKeyName : primaryKeyNames) {
200 queryParams.add(entityType + "." + primaryKeyName + ":" + urlPathElements[index]);
203 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
204 "Could not extract query parametrs for entity-type = '" + entityType
205 + "' from self-link = " + link);
212 } catch (URISyntaxException exc) {
214 LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR,
215 "Error extracting query parameters from self-link = " + link + ". Error = "
224 * Decode complex attribute group.
227 * @param attributeGroup the attribute group
228 * @return boolean indicating whether operation was successful (true), / failure(false).
230 public boolean decodeComplexAttributeGroup(ActiveInventoryNode ain, JsonNode attributeGroup) {
234 Iterator<Entry<String, JsonNode>> entityArrays = attributeGroup.fields();
235 Entry<String, JsonNode> entityArray = null;
237 if (entityArrays == null) {
238 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, attributeGroup.toString());
239 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
243 while (entityArrays.hasNext()) {
245 entityArray = entityArrays.next();
247 String entityType = entityArray.getKey();
248 JsonNode entityArrayObject = entityArray.getValue();
250 if (entityArrayObject.isArray()) {
252 Iterator<JsonNode> entityCollection = entityArrayObject.elements();
253 JsonNode entity = null;
254 while (entityCollection.hasNext()) {
255 entity = entityCollection.next();
257 if (LOG.isDebugEnabled()) {
258 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "decodeComplexAttributeGroup(),"
259 + " entity = " + entity.toString());
263 * Here's what we are going to do:
265 * <li>In the ActiveInventoryNode, on construction maintain a collection of queryParams
266 * that is added to for the purpose of discovering parent->child hierarchies.
268 * <li>When we hit this block of the code then we'll use the queryParams to feed the
269 * generic query to resolve the self-link asynchronously.
271 * <li>Upon successful link determination, then and only then will we create a new node
272 * in the nodeCache and process the child
276 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
277 newNode.setEntityType(entityType);
280 * This is partially a lie because we actually don't have a self-link for complex nodes
281 * discovered in this way.
283 newNode.setSelfLinkProcessed(true);
284 newNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
285 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
288 * copy parent query params into new child
291 if (SERVICE_INSTANCE.equals(entityType)) {
294 * 1707 AAI has an issue being tracked with AAI-8932 where the generic-query cannot be
295 * resolved if all the service-instance path keys are provided. The query only works
296 * if only the service-instance key and valude are passed due to a historical reason.
297 * A fix is being worked on for 1707, and when it becomes available we can revert this
301 newNode.clearQueryParams();
306 * For all other entity-types we want to copy the parent query parameters into the new node
310 for (String queryParam : ain.getQueryParams()) {
311 newNode.addQueryParam(queryParam);
317 if (!addComplexGroupToNode(newNode, entity)) {
318 LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, "Failed to add child to parent for child = " + entity.toString());
321 if (!addNodeQueryParams(newNode)) {
322 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Error determining node id and key for node = " + newNode.dumpNodeTree(true)
323 + " skipping relationship processing");
324 newNode.changeState(NodeProcessingState.ERROR,
325 NodeProcessingAction.NODE_IDENTITY_ERROR);
329 newNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
330 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
336 * Order matters for the query params. We need to set the parent ones before the child
340 String selfLinkQuery =
341 aaiAdapter.getGenericQueryForSelfLink(entityType, newNode.getQueryParams());
344 * <li>get the self-link
345 * <li>add it to the new node
346 * <li>generate node id
347 * <li>add node to node cache
348 * <li>add node id to parent outbound links list
349 * <li>process node children (should be automatic) (but don't query and resolve
350 * self-link as we already have all the data)
353 SelfLinkDeterminationTransaction txn = new SelfLinkDeterminationTransaction();
355 txn.setQueryString(selfLinkQuery);
356 txn.setNewNode(newNode);
357 txn.setParentNodeId(ain.getNodeId());
358 aaiWorkOnHand.incrementAndGet();
359 supplyAsync(new PerformSelfLinkDeterminationTask(txn, null, aaiAdapter),
360 aaiExecutorService).whenComplete((nodeTxn, error) -> {
363 LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_GENERIC, selfLinkQuery);
366 OperationResult opResult = nodeTxn.getOpResult();
368 ActiveInventoryNode newChildNode = txn.getNewNode();
370 if (opResult != null && opResult.wasSuccessful()) {
372 if (!opResult.wasSuccessful()) {
373 numFailedLinkResolve.incrementAndGet();
376 if (opResult.isFromCache()) {
377 numSuccessfulLinkResolveFromCache.incrementAndGet();
379 numSuccessfulLinkResolveFromFromServer.incrementAndGet();
383 * extract the self-link from the operational result.
386 Collection<JsonNode> entityLinks = new ArrayList<JsonNode>();
387 JsonNode genericQueryResult = null;
390 NodeUtils.convertJsonStrToJsonNode(nodeTxn.getOpResult().getResult());
391 } catch (Exception exc) {
392 LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, JsonNode.class.toString(), exc.getMessage());
395 NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link",
398 String selfLink = null;
400 if (entityLinks.size() != 1) {
402 LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS, String.valueOf(entityLinks.size()));
405 selfLink = ((JsonNode) entityLinks.toArray()[0]).asText();
406 selfLink = ActiveInventoryAdapter.extractResourcePath(selfLink);
408 newChildNode.setSelfLink(selfLink);
409 newChildNode.setNodeId(NodeUtils.generateUniqueShaDigest(selfLink));
411 String uri = NodeUtils.calculateEditAttributeUri(selfLink);
413 newChildNode.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
416 ActiveInventoryNode parent = nodeCache.get(txn.getParentNodeId());
418 if (parent != null) {
419 parent.addOutboundNeighbor(newChildNode.getNodeId());
420 newChildNode.addInboundNeighbor(parent.getNodeId());
423 newChildNode.setSelfLinkPendingResolve(false);
424 newChildNode.setSelfLinkProcessed(true);
425 newChildNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
426 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
428 nodeCache.putIfAbsent(newChildNode.getNodeId(), newChildNode);
433 LOG.error(AaiUiMsgs.SELF_LINK_RETRIEVAL_FAILED, txn.getQueryString(),
434 String.valueOf(nodeTxn.getOpResult().getResultCode()), nodeTxn.getOpResult().getResult());
435 newChildNode.setSelflinkRetrievalFailure(true);
436 newChildNode.setSelfLinkProcessed(true);
437 newChildNode.setSelfLinkPendingResolve(false);
439 newChildNode.changeState(NodeProcessingState.ERROR,
440 NodeProcessingAction.SELF_LINK_DETERMINATION_ERROR);
446 aaiWorkOnHand.decrementAndGet();
455 LOG.error(AaiUiMsgs.UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE, entityType);
459 } catch (Exception exc) {
460 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Exception caught while"
461 + " decoding complex attribute group - " + exc.getMessage());
469 * Process self link response.
471 * @param nodeId the node id
473 protected void processSelfLinkResponse(String nodeId) {
475 if (nodeId == null) {
476 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link"
477 + " response because nodeId is null");
481 ActiveInventoryNode ain = nodeCache.get(nodeId);
484 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link response"
485 + " because can't find node for id = " + nodeId);
489 JsonNode jsonNode = null;
492 jsonNode = mapper.readValue(ain.getOpResult().getResult(), JsonNode.class);
493 } catch (Exception exc) {
494 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
495 + " response str into JsonNode with error, " + exc.getLocalizedMessage());
496 ain.changeState(NodeProcessingState.ERROR,
497 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
501 if (jsonNode == null) {
502 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse json node str."
503 + " Parse resulted a null value.");
504 ain.changeState(NodeProcessingState.ERROR,
505 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
509 Iterator<Entry<String, JsonNode>> fieldNames = jsonNode.fields();
510 Entry<String, JsonNode> field = null;
512 RelationshipList relationshipList = null;
514 while (fieldNames.hasNext()) {
516 field = fieldNames.next();
517 String fieldName = field.getKey();
519 if ("relationship-list".equals(fieldName)) {
522 relationshipList = mapper.readValue(field.getValue().toString(), RelationshipList.class);
524 if (relationshipList != null) {
525 ain.addRelationshipList(relationshipList);
528 } catch (Exception exc) {
529 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse relationship-list"
530 + " attribute. Parse resulted in error, " + exc.getLocalizedMessage());
531 ain.changeState(NodeProcessingState.ERROR,
532 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
538 JsonNode nodeValue = field.getValue();
540 if(nodeValue!=null) {
541 if (nodeValue.isValueNode()) {
542 String key = fieldName;
543 handleNodeValue(ain, fieldName, key, nodeValue.asText());
544 } else if (nodeValue.isArray()) {
545 String key = field.getKey();
546 handleNodeValue(ain, fieldName, key, nodeValue.toString());
548 ain.addComplexGroup(nodeValue);
556 String uri = NodeUtils.calculateEditAttributeUri(ain.getSelfLink());
558 ain.addProperty(SparkyConstants.URI_ATTR_NAME, uri);
562 * We need a special behavior for intermediate entities from the REST model
564 * Tenants are not top level entities, and when we want to visualization
565 * their children, we need to construct keys that include the parent entity query
566 * keys, the current entity type keys, and the child keys. We'll always have the
567 * current entity and children, but never the parent entity in the current (1707) REST
570 * We have two possible solutions:
572 * 1) Try to use the custom-query approach to learn about the entity keys
573 * - this could be done, but it could be very expensive for large objects. When we do the first
574 * query to get a tenant, it will list all the in and out edges related to this entity,
575 * there is presently no way to filter this. But the approach could be made to work and it would be
576 * somewhat data-model driven, other than the fact that we have to first realize that the entity
577 * that is being searched for is not top-level entity. Once we have globally unique ids for resources
578 * this logic will not be needed and everything will be simpler. The only reason we are in this logic
579 * at all is to be able to calculate a url for the child entities so we can hash it to generate
580 * a globally unique id that can be safely used for the node.
582 * *2* Extract the keys from the pathed self-link.
583 * This is a bad solution and I don't like it but it will be fast for all resource types, as the
584 * information is already encoded in the URI. When we get to a point where we switch to a better
585 * globally unique entity identity model, then a lot of the code being used to calculate an entity url
586 * to in-turn generate a deterministic globally unique id will disappear.
589 * right now we have the following:
591 * - cloud-regions/cloud-region/{cloud-region-id}/{cloud-owner-id}/tenants/tenant/{tenant-id}
596 * For all entity types use the self-link extraction method to be consistent. Once we have a
597 * globally unique identity mechanism for entities, this logic can be revisited.
599 ain.clearQueryParams();
600 ain.addQueryParams(extractQueryParamsFromSelfLink(ain.getSelfLink()));
601 ain.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED,
602 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
607 protected void handleNodeValue(ActiveInventoryNode ain, String fieldName, String key, String value) {
608 if (oxmEntityLookup.getEntityDescriptors().get(fieldName) == null) {
611 * entity property name is not an entity, thus we can add this property name and value
612 * to our property set
615 ain.addProperty(key, value);
621 * Perform self link resolve.
623 * @param nodeId the node id
625 protected void performSelfLinkResolve(String nodeId) {
627 if (nodeId == null) {
628 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Resolve of self-link"
629 + " has been skipped because provided nodeId is null");
633 ActiveInventoryNode ain = nodeCache.get(nodeId);
636 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId
637 + ", from node cache. Resolve self-link method has been skipped.");
641 if (!ain.isSelfLinkPendingResolve()) {
643 ain.setSelfLinkPendingResolve(true);
645 // kick off async self-link resolution
647 if (LOG.isDebugEnabled()) {
648 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
649 "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink());
652 numLinksDiscovered.incrementAndGet();
654 String depthModifier = DEPTH_ALL_MODIFIER;
657 * If the current node is the search target, we want to see everything the node has to offer
658 * from the self-link and not filter it to a single node.
661 if (visualizationConfigs.getShallowEntities().contains(ain.getEntityType())
662 && !ain.isRootNode()) {
663 depthModifier = NODES_ONLY_MODIFIER;
666 NodeProcessingTransaction txn = new NodeProcessingTransaction();
667 txn.setProcessingNode(ain);
668 txn.setRequestParameters(depthModifier);
669 aaiWorkOnHand.incrementAndGet();
671 new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiAdapter),
672 aaiExecutorService).whenComplete((nodeTxn, error) -> {
677 * an error processing the self link should probably result in the node processing
678 * state shifting to ERROR
681 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
683 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
684 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
686 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
690 totalLinksRetrieved.incrementAndGet();
692 OperationResult opResult = nodeTxn.getOpResult();
694 if (opResult != null && opResult.wasSuccessful()) {
696 if (!opResult.wasSuccessful()) {
697 numFailedLinkResolve.incrementAndGet();
700 if (opResult.isFromCache()) {
701 numSuccessfulLinkResolveFromCache.incrementAndGet();
703 numSuccessfulLinkResolveFromFromServer.incrementAndGet();
707 nodeTxn.getProcessingNode().setOpResult(opResult);
708 nodeTxn.getProcessingNode().changeState(
709 NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
710 NodeProcessingAction.SELF_LINK_RESOLVE_OK);
712 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
713 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
716 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Self Link retrieval for link,"
717 + txn.getSelfLinkWithModifiers() + ", failed with error code,"
718 + nodeTxn.getOpResult().getResultCode() + ", and message,"
719 + nodeTxn.getOpResult().getResult());
721 nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
722 nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
724 nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
725 NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
727 nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
732 aaiWorkOnHand.decrementAndGet();
744 * @param nodeId the node id
746 protected void processNeighbors(String nodeId) {
748 if (nodeId == null) {
749 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
750 + " neighbors because nodeId is null.");
754 ActiveInventoryNode ain = nodeCache.get(nodeId);
757 LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process"
758 + " neighbors because node could not be found in nodeCache with id, " + nodeId);
763 * process complex attribute and relationships
766 boolean neighborsProcessedSuccessfully = true;
768 for (JsonNode n : ain.getComplexGroups()) {
769 neighborsProcessedSuccessfully &= decodeComplexAttributeGroup(ain, n);
772 for (RelationshipList relationshipList : ain.getRelationshipLists()) {
773 neighborsProcessedSuccessfully &= addSelfLinkRelationshipChildren(ain, relationshipList);
777 if (neighborsProcessedSuccessfully) {
778 ain.changeState(NodeProcessingState.READY, NodeProcessingAction.NEIGHBORS_PROCESSED_OK);
780 ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
785 * If neighbors fail to process, there is already a call to change the state within the
786 * relationship and neighbor processing functions.
792 * Find and mark root node.
794 * @param queryParams the query params
795 * @return true, if successful
797 protected void findAndMarkRootNode(QueryParams queryParams) {
799 if (isRootNodeFound()) {
803 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
805 if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
806 cacheNode.setNodeDepth(0);
807 cacheNode.setRootNode(true);
808 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
809 setRootNodeFound(true);
816 * Process current node states.
819 protected void processCurrentNodeStates(QueryParams queryParams) {
821 * Force an evaluation of node depths before determining if we should limit state-based
822 * traversal or processing.
825 findAndMarkRootNode(queryParams);
827 verifyOutboundNeighbors();
829 for (ActiveInventoryNode cacheNode : nodeCache.values()) {
831 if (LOG.isDebugEnabled()) {
832 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
833 "processCurrentNodeState(), nid = "
834 + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
837 switch (cacheNode.getState()) {
840 processInitialState(cacheNode.getNodeId());
849 case SELF_LINK_UNRESOLVED: {
850 performSelfLinkResolve(cacheNode.getNodeId());
854 case SELF_LINK_RESPONSE_UNPROCESSED: {
855 processSelfLinkResponse(cacheNode.getNodeId());
859 case NEIGHBORS_UNPROCESSED: {
862 * We use the rootNodeDiscovered flag to ignore depth retrieval thresholds until the root
863 * node is identified. Then the evaluative depth calculations should re-balance the graph
864 * around the root node.
867 if (!isRootNodeFound() || cacheNode.getNodeDepth() < this.visualizationConfigs
868 .getMaxSelfLinkTraversalDepth()) {
870 if (LOG.isDebugEnabled()) {
871 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
872 "processCurrentNodeState() -- Node at max depth,"
873 + " halting processing at current state = -- "
874 + cacheNode.getState() + " nodeId = " + cacheNode.getNodeId());
877 processNeighbors(cacheNode.getNodeId());
892 * Adds the complex group to node.
894 * @param targetNode the target node
895 * @param attributeGroup the attribute group
896 * @return true, if successful
898 protected boolean addComplexGroupToNode(ActiveInventoryNode targetNode, JsonNode attributeGroup) {
900 if (attributeGroup == null) {
901 targetNode.changeState(NodeProcessingState.ERROR,
902 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK);
906 RelationshipList relationshipList = null;
908 if (attributeGroup.isObject()) {
910 Iterator<Entry<String, JsonNode>> fields = attributeGroup.fields();
911 Entry<String, JsonNode> field = null;
915 while (fields.hasNext()) {
916 field = fields.next();
917 fieldName = field.getKey();
918 fieldValue = field.getValue();
920 if (fieldValue.isObject()) {
922 if (fieldName.equals("relationship-list")) {
926 mapper.readValue(field.getValue().toString(), RelationshipList.class);
928 if (relationshipList != null) {
929 targetNode.addRelationshipList(relationshipList);
932 } catch (Exception exc) {
933 LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse"
934 + " relationship-list attribute. Parse resulted in error, "
935 + exc.getLocalizedMessage());
936 targetNode.changeState(NodeProcessingState.ERROR,
937 NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_ERROR);
942 targetNode.addComplexGroup(fieldValue);
945 } else if (fieldValue.isArray()) {
946 if (LOG.isDebugEnabled()) {
947 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
948 "Unexpected array type with a key = " + fieldName);
950 } else if (fieldValue.isValueNode()) {
951 if (oxmEntityLookup.getEntityDescriptors().get(field.getKey()) == null) {
953 * property key is not an entity type, add it to our property set.
955 targetNode.addProperty(field.getKey(), fieldValue.asText());
961 } else if (attributeGroup.isArray()) {
962 if (LOG.isDebugEnabled()) {
963 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
964 "Unexpected array type for attributeGroup = " + attributeGroup);
966 } else if (attributeGroup.isValueNode()) {
967 if (LOG.isDebugEnabled()) {
968 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
969 "Unexpected value type for attributeGroup = " + attributeGroup);
976 public int getNumSuccessfulLinkResolveFromCache() {
977 return numSuccessfulLinkResolveFromCache.get();
980 public int getNumSuccessfulLinkResolveFromFromServer() {
981 return numSuccessfulLinkResolveFromFromServer.get();
984 public int getNumFailedLinkResolve() {
985 return numFailedLinkResolve.get();
988 public void setMaxSelfLinkTraversalDepth(int depth) {
989 this.maxSelfLinkTraversalDepth = depth;
992 public int getMaxSelfLinkTraversalDepth() {
993 return this.maxSelfLinkTraversalDepth;
996 public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
1001 * Gets the relationship primary key values.
1004 * @param entityType the entity type
1005 * @param pkeyNames the pkey names
1006 * @return the relationship primary key values
1008 protected String getRelationshipPrimaryKeyValues(Relationship r, String entityType,
1009 List<String> pkeyNames) {
1011 StringBuilder sb = new StringBuilder(64);
1013 if (pkeyNames.size() > 0) {
1014 String primaryKey = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(0));
1015 if (primaryKey != null) {
1017 sb.append(primaryKey);
1020 // this should be a fatal error because unless we can
1021 // successfully retrieve all the expected keys we'll end up
1022 // with a garbage node
1023 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract"
1024 + " keyName, " + entityType + "." + pkeyNames.get(0)
1025 + ", from relationship data, " + r.toString());
1029 for (int i = 1; i < pkeyNames.size(); i++) {
1031 String kv = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(i));
1033 sb.append("/").append(kv);
1035 // this should be a fatal error because unless we can
1036 // successfully retrieve all the expected keys we'll end up
1037 // with a garbage node
1038 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: failed to extract keyName, "
1039 + entityType + "." + pkeyNames.get(i)
1040 + ", from relationship data, " + r.toString());
1045 return sb.toString();
1054 * Extract key value from relation data.
1057 * @param keyName the key name
1058 * @return the string
1060 protected String extractKeyValueFromRelationData(Relationship r, String keyName) {
1062 RelationshipData[] rdList = r.getRelationshipData();
1064 for (RelationshipData relData : rdList) {
1066 if (relData.getRelationshipKey().equals(keyName)) {
1067 return relData.getRelationshipValue();
1075 * Determine node id and key.
1077 * @param ain the ain
1078 * @return true, if successful
1080 protected boolean addNodeQueryParams(ActiveInventoryNode ain) {
1083 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "ActiveInventoryNode is null");
1087 List<String> pkeyNames =
1088 oxmEntityLookup.getEntityDescriptors().get(ain.getEntityType()).getPrimaryKeyAttributeNames();
1090 if (pkeyNames == null || pkeyNames.size() == 0) {
1091 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Primary key names is empty");
1095 StringBuilder sb = new StringBuilder(64);
1097 if (pkeyNames.size() > 0) {
1098 String primaryKey = ain.getProperties().get(pkeyNames.get(0));
1099 if (primaryKey != null) {
1100 sb.append(primaryKey);
1102 // this should be a fatal error because unless we can
1103 // successfully retrieve all the expected keys we'll end up
1104 // with a garbage node
1105 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
1106 + pkeyNames.get(0) + ", from entity properties");
1110 for (int i = 1; i < pkeyNames.size(); i++) {
1112 String kv = ain.getProperties().get(pkeyNames.get(i));
1114 sb.append("/").append(kv);
1116 // this should be a fatal error because unless we can
1117 // successfully retrieve all the expected keys we'll end up
1118 // with a garbage node
1119 LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, "
1120 + pkeyNames.get(i) + ", from entity properties");
1125 /*final String nodeId = NodeUtils.generateUniqueShaDigest(ain.getEntityType(),
1126 NodeUtils.concatArray(pkeyNames, "/"), sb.toString());*/
1128 //ain.setNodeId(nodeId);
1129 ain.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1130 ain.setPrimaryKeyValue(sb.toString());
1132 if (ain.getEntityType() != null && ain.getPrimaryKeyName() != null
1133 && ain.getPrimaryKeyValue() != null) {
1135 ain.getEntityType() + "." + ain.getPrimaryKeyName() + ":" + ain.getPrimaryKeyValue());
1146 * Adds the self link relationship children.
1148 * @param processingNode the processing node
1149 * @param relationshipList the relationship list
1150 * @return true, if successful
1152 protected boolean addSelfLinkRelationshipChildren(ActiveInventoryNode processingNode,
1153 RelationshipList relationshipList) {
1155 if (relationshipList == null) {
1156 LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "No relationships added to parent node = "
1157 + processingNode.getNodeId() + " because relationshipList is empty");
1158 processingNode.changeState(NodeProcessingState.ERROR,
1159 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1163 Relationship[] relationshipArray = relationshipList.getRelationshipList();
1164 OxmEntityDescriptor descriptor = null;
1166 if (relationshipArray != null) {
1168 ActiveInventoryNode newNode = null;
1169 String resourcePath = null;
1171 for (Relationship r : relationshipArray) {
1173 resourcePath = ActiveInventoryAdapter.extractResourcePath(r.getRelatedLink());
1175 String nodeId = NodeUtils.generateUniqueShaDigest(resourcePath);
1177 if (nodeId == null) {
1179 LOG.error(AaiUiMsgs.SKIPPING_RELATIONSHIP, r.toString());
1180 processingNode.changeState(NodeProcessingState.ERROR,
1181 NodeProcessingAction.NODE_IDENTITY_ERROR);
1185 newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
1187 String entityType = r.getRelatedTo();
1189 if (r.getRelationshipData() != null) {
1190 for (RelationshipData rd : r.getRelationshipData()) {
1191 newNode.addQueryParam(rd.getRelationshipKey() + ":" + rd.getRelationshipValue());
1195 descriptor = oxmEntityLookup.getEntityDescriptors().get(r.getRelatedTo());
1197 newNode.setNodeId(nodeId);
1198 newNode.setEntityType(entityType);
1199 newNode.setSelfLink(resourcePath);
1201 processingNode.addOutboundNeighbor(nodeId);
1203 if (descriptor != null) {
1205 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1207 newNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1208 NodeProcessingAction.SELF_LINK_SET);
1210 newNode.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/"));
1212 String primaryKeyValues = getRelationshipPrimaryKeyValues(r, entityType, pkeyNames);
1213 newNode.setPrimaryKeyValue(primaryKeyValues);
1217 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR,
1218 "Failed to parse entity because OXM descriptor could not be found for type = "
1219 + r.getRelatedTo());
1221 newNode.changeState(NodeProcessingState.ERROR,
1222 NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR);
1226 if (nodeCache.putIfAbsent(nodeId, newNode) != null) {
1227 if (LOG.isDebugEnabled()) {
1228 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1229 "Failed to add node to nodeCache because it already exists. Node id = "
1230 + newNode.getNodeId());
1243 * Process initial state.
1245 * @param nodeId the node id
1247 protected void processInitialState(String nodeId) {
1249 if (nodeId == null) {
1250 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
1254 ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
1256 if (cachedNode == null) {
1257 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node cannot be"
1258 + " found for nodeId, " + nodeId);
1262 if (cachedNode.getSelfLink() == null) {
1264 if (cachedNode.getNodeId() == null ) {
1267 * if the self link is null at the INIT state, which could be valid if this node is a
1268 * complex attribute group which didn't originate from a self-link, but in that situation
1269 * both the node id and node key should already be set.
1272 cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
1276 if (cachedNode.getNodeId() != null) {
1279 * This should be the success path branch if the self-link is not set
1282 cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
1283 NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
1289 if (cachedNode.hasResolvedSelfLink()) {
1290 LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
1291 cachedNode.changeState(NodeProcessingState.ERROR,
1292 NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
1294 cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
1295 NodeProcessingAction.SELF_LINK_SET);
1301 * Process skeleton node.
1303 * @param skeletonNode the skeleton node
1304 * @param queryParams the query params
1306 protected void processSearchableEntity(SearchableEntity searchTargetEntity, QueryParams queryParams) {
1308 if (searchTargetEntity == null) {
1312 if (searchTargetEntity.getId() == null) {
1313 LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
1314 + " node because nodeId is null for node, " + searchTargetEntity.getLink());
1318 ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
1320 newNode.setNodeId(searchTargetEntity.getId());
1321 newNode.setEntityType(searchTargetEntity.getEntityType());
1322 newNode.setPrimaryKeyName(getEntityTypePrimaryKeyName(searchTargetEntity.getEntityType()));
1323 newNode.setPrimaryKeyValue(searchTargetEntity.getEntityPrimaryKeyValue());
1325 if (newNode.getEntityType() != null && newNode.getPrimaryKeyName() != null
1326 && newNode.getPrimaryKeyValue() != null) {
1327 newNode.addQueryParam(
1328 newNode.getEntityType() + "." + newNode.getPrimaryKeyName() + ":" + newNode.getPrimaryKeyValue());
1331 * This code may need some explanation. In any graph there will be a single root node. The root
1332 * node is really the center of the universe, and for now, we are tagging the search target as
1333 * the root node. Everything else in the visualization of the graph will be centered around this
1334 * node as the focal point of interest.
1336 * Due to it's special nature, there will only ever be one root node, and it's node depth will
1337 * always be equal to zero.
1340 if (!isRootNodeFound()) {
1341 if (queryParams.getSearchTargetNodeId().equals(newNode.getNodeId())) {
1342 newNode.setNodeDepth(0);
1343 newNode.setRootNode(true);
1344 LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
1345 setRootNodeFound(true);
1349 newNode.setSelfLink(searchTargetEntity.getLink());
1351 nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
1354 protected int getTotalWorkOnHand() {
1356 int numNodesWithPendingStates = 0;
1358 if( isRootNodeFound()) {
1359 evaluateNodeDepths();
1362 for (ActiveInventoryNode n : nodeCache.values()) {
1364 switch (n.getState()) {
1368 // do nothing, these are our normal
1373 case NEIGHBORS_UNPROCESSED: {
1375 if (n.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
1377 * Only process our neighbors relationships if our current depth is less than the max
1380 numNodesWithPendingStates++;
1389 * for all other states, there is work to be done
1391 numNodesWithPendingStates++;
1398 LOG.debug(AaiUiMsgs.OUTSTANDING_WORK_PENDING_NODES,
1399 String.valueOf(numNodesWithPendingStates));
1401 int totalWorkOnHand = aaiWorkOnHand.get() + numNodesWithPendingStates;
1403 return totalWorkOnHand;
1408 * Checks for out standing work.
1410 * @return true, if successful
1412 protected void processOutstandingWork(QueryParams queryParams) {
1414 while (getTotalWorkOnHand() > 0) {
1417 * Force an evaluation of node depths before determining if we should limit state-based
1418 * traversal or processing.
1421 processCurrentNodeStates(queryParams);
1425 } catch (InterruptedException exc) {
1426 LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
1427 Thread.currentThread().interrupt();
1436 * @see org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai.sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams)
1439 public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
1444 if (searchtargetEntity == null) {
1445 LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, contextIdStr + " - Failed to"
1446 + " processSelfLinks, searchtargetEntity is null");
1450 long startTimeInMs = System.currentTimeMillis();
1452 processSearchableEntity(searchtargetEntity, queryParams);
1455 * This method is blocking until we decouple it with a CountDownLatch await condition,
1456 * and make the internal graph processing more event-y.
1459 processOutstandingWork(queryParams);
1461 long totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
1463 long opTime = System.currentTimeMillis() - startTimeInMs;
1465 LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
1466 String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
1468 } catch (Exception exc) {
1469 LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
1475 * Verify outbound neighbors.
1477 protected void verifyOutboundNeighbors() {
1479 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1481 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1483 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1485 if (targetNode != null && srcNode.getNodeId() != null) {
1487 targetNode.addInboundNeighbor(srcNode.getNodeId());
1489 if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
1490 targetNode.addOutboundNeighbor(srcNode.getNodeId());
1502 * Evaluate node depths.
1504 protected void evaluateNodeDepths() {
1506 int numChanged = -1;
1507 int numAttempts = 0;
1509 while (numChanged != 0) {
1514 for (ActiveInventoryNode srcNode : nodeCache.values()) {
1516 if (srcNode.getState() == NodeProcessingState.INIT) {
1519 * this maybe the only state that we don't want to to process the node depth on, because
1520 * typically it won't have any valid fields set, and it may remain in a partial state
1521 * until we have processed the self-link.
1528 for (String targetNodeId : srcNode.getOutboundNeighbors()) {
1529 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1531 if (targetNode != null) {
1533 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1539 for (String targetNodeId : srcNode.getInboundNeighbors()) {
1540 ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
1542 if (targetNode != null) {
1544 if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
1551 if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
1552 LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
1558 if (LOG.isDebugEnabled()) {
1559 if (numAttempts > 0) {
1560 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1561 "Evaluate node depths completed in " + numAttempts + " attempts");
1563 LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
1564 "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
1572 * Gets the entity type primary key name.
1574 * @param entityType the entity type
1575 * @return the entity type primary key name
1579 protected String getEntityTypePrimaryKeyName(String entityType) {
1581 if (entityType == null) {
1582 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary key"
1583 + " name because entity type is null");
1587 OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
1589 if (descriptor == null) {
1590 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "oxm entity"
1591 + " descriptor for entityType = " + entityType);
1595 List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
1597 if (pkeyNames == null || pkeyNames.size() == 0) {
1598 LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary"
1599 + " key because descriptor primary key names is empty");
1603 return NodeUtils.concatArray(pkeyNames, "/");