Integrate Sparky-BE with Gizmo 17/34817/1
authorda490c <dave.adams@amdocs.com>
Thu, 8 Mar 2018 17:54:22 +0000 (12:54 -0500)
committerda490c <dave.adams@amdocs.com>
Thu, 8 Mar 2018 17:59:30 +0000 (12:59 -0500)
Issue-ID: AAI-847
Change-Id: I8c61e681c4f427f6e567dd8498b6f5f7baa19c85
Signed-off-by: da490c <dave.adams@amdocs.com>
17 files changed:
src/main/java/org/onap/aai/sparky/dal/GizmoAdapter.java [new file with mode: 0644]
src/main/java/org/onap/aai/sparky/sync/task/PerformGizmoRetrieval.java [new file with mode: 0644]
src/main/java/org/onap/aai/sparky/util/NodeUtils.java
src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java
src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java
src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoEntity.java [new file with mode: 0644]
src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipEntity.java [new file with mode: 0644]
src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipHint.java [new file with mode: 0644]
src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseGizmoVisualizationContext.java [new file with mode: 0644]
src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseVisualizationService.java
src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformGizmoNodeSelfLinkProcessingTask.java [new file with mode: 0644]
src/test/java/org/onap/aai/sparky/config/oxm/OxmEntityContainerLookup.java [new file with mode: 0644]
src/test/java/org/onap/aai/sparky/synchronizer/GizmoEntitySummarizer.java [new file with mode: 0644]
src/test/java/org/onap/aai/sparky/util/OxmModelAndProcessorHelper.java
src/test/java/org/onap/aai/sparky/viewandinspect/BaseVisualizationServiceTest.java
src/test/java/org/onap/aai/sparky/viewandinspect/sync/ViewInspectGizmoEntitySynchronizer.java [new file with mode: 0644]
src/test/java/org/onap/aai/sparky/viewandinspect/sync/ViewInspectGizmoSyncController.java [new file with mode: 0644]

diff --git a/src/main/java/org/onap/aai/sparky/dal/GizmoAdapter.java b/src/main/java/org/onap/aai/sparky/dal/GizmoAdapter.java
new file mode 100644 (file)
index 0000000..99a967c
--- /dev/null
@@ -0,0 +1,334 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.dal;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriBuilder;
+
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.aai.restclient.client.OperationResult;
+import org.onap.aai.restclient.client.RestClient;
+import org.onap.aai.restclient.enums.RestAuthenticationMode;
+import org.onap.aai.sparky.config.oxm.OxmModelLoader;
+import org.onap.aai.sparky.dal.exception.ElasticSearchOperationException;
+import org.onap.aai.sparky.dal.rest.RestClientConstructionException;
+import org.onap.aai.sparky.dal.rest.RestClientFactory;
+import org.onap.aai.sparky.dal.rest.config.RestEndpointConfig;
+import org.onap.aai.sparky.logging.AaiUiMsgs;
+import org.onap.aai.sparky.util.NodeUtils;
+
+/**
+ * The Class GizmoAdapter.
+ */
+
+public class GizmoAdapter {
+
+       private static final Logger LOG = LoggerFactory.getInstance().getLogger(GizmoAdapter.class);
+
+       private static final String HEADER_TRANS_ID = "X-TransactionId";
+       private static final String HEADER_FROM_APP_ID = "X-FromAppId";
+       private static final String HEADER_AUTHORIZATION = "Authorization";
+
+       private static final String HTTP_SCHEME = "http";
+       private static final String HTTPS_SCHEME = "https";
+
+       private static final String TRANSACTION_ID_PREFIX = "txnId-";
+       private static final String UI_APP_NAME = "AAI-UI";
+
+       private OxmModelLoader oxmModelLoader;
+
+       private RestEndpointConfig endpointConfig;
+
+       private RestClient restClient;
+
+       private String inventoryBasePath;
+       private String relationshipsBasePath;
+
+       /**
+        * Instantiates a new active inventory adapter.
+        * 
+        * @throws RestClientConstructionException
+        *
+        */
+
+       public GizmoAdapter(OxmModelLoader oxmModelLoader, RestEndpointConfig endpointConfig)
+                       throws ElasticSearchOperationException, IOException, RestClientConstructionException {
+
+               this.oxmModelLoader = oxmModelLoader;
+               this.endpointConfig = endpointConfig;
+               this.restClient = RestClientFactory.buildClient(endpointConfig);
+
+       }
+
+       public String getRelationshipsBasePath() {
+               return relationshipsBasePath;
+       }
+
+       public void setRelationshipsBasePath(String relationshipsBasePath) {
+               this.relationshipsBasePath = relationshipsBasePath;
+       }
+
+       public String getInventoryBasePath() {
+               return inventoryBasePath;
+       }
+
+       public void setInventoryBasePath(String inventoryBasePath) {
+               this.inventoryBasePath = inventoryBasePath;
+       }
+
+       public String getFullInventoryUrl(String resourceUrl) throws Exception {
+               final String host = endpointConfig.getEndpointIpAddress();
+               final String port = endpointConfig.getEndpointServerPort();
+               final String basePath = getInventoryBasePath();
+               return String.format("https://%s:%s%s%s", host, port, basePath, resourceUrl);
+       }
+
+       public String addServerDetailsToUrl(String resourceUrl) throws Exception {
+               final String host = endpointConfig.getEndpointIpAddress();
+               final String port = endpointConfig.getEndpointServerPort();
+               return String.format("https://%s:%s/%s", host, port, resourceUrl);
+       }
+
+       public String getFullRelationshipUrl(String resourceUrl) throws Exception {
+               final String host = endpointConfig.getEndpointIpAddress();
+               final String port = endpointConfig.getEndpointServerPort();
+               final String basePath = getRelationshipsBasePath();
+               return String.format("https://%s:%s%s%s", host, port, basePath, resourceUrl);
+       }
+
+       protected Map<String, List<String>> getMessageHeaders() {
+
+               Map<String, List<String>> headers = new HashMap<String, List<String>>();
+
+               headers.putIfAbsent(HEADER_FROM_APP_ID, new ArrayList<String>());
+               headers.get(HEADER_FROM_APP_ID).add(UI_APP_NAME);
+
+               headers.putIfAbsent(HEADER_TRANS_ID, new ArrayList<String>());
+               headers.get(HEADER_TRANS_ID).add(TRANSACTION_ID_PREFIX + NodeUtils.getRandomTxnId());
+
+               if (endpointConfig.getRestAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) {
+
+                       headers.putIfAbsent(HEADER_AUTHORIZATION, new ArrayList<String>());
+                       headers.get(HEADER_AUTHORIZATION).add(getBasicAuthenticationCredentials());
+
+               }
+
+               return headers;
+       }
+
+       protected String getBasicAuthenticationCredentials() {
+               String usernameAndPassword = String.join(":", endpointConfig.getBasicAuthUserName(),
+                               endpointConfig.getBasicAuthPassword());
+               return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes());
+       }
+
+       /**
+        * Our retry conditions should be very specific.
+        *
+        * @param r
+        *            the r
+        * @return true, if successful
+        */
+       private boolean shouldRetryRequest(OperationResult r) {
+
+               if (r == null) {
+                       return true;
+               }
+
+               int rc = r.getResultCode();
+
+               if (rc == 200) {
+                       return false;
+               }
+
+               if (rc == 404) {
+                       return false;
+               }
+
+               return true;
+
+       }
+
+       /**
+        * Query active inventory.
+        *
+        * @param url
+        *            the url
+        * @param acceptContentType
+        *            the accept content type
+        * @return the operation result
+        */
+       OperationResult queryGizmo(String url, String acceptContentType) {
+
+               return restClient.get(url, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE);
+
+       }
+
+       public RestEndpointConfig getEndpointConfig() {
+               return endpointConfig;
+       }
+
+       public void setEndpointConfig(RestEndpointConfig endpointConfig) {
+               this.endpointConfig = endpointConfig;
+       }
+
+       public OperationResult queryGizmoWithRetries(String url, String responseType, int numRetries) {
+
+               OperationResult result = null;
+
+               for (int retryCount = 0; retryCount < numRetries; retryCount++) {
+
+                       LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_SEQ, url, String.valueOf(retryCount + 1));
+
+                       result = queryGizmo(url, responseType);
+
+                       /**
+                        * Record number of times we have attempted the request to later
+                        * summarize how many times we are generally retrying over thousands
+                        * of messages in a sync.
+                        * 
+                        * If the number of retries is surprisingly high, then we need to
+                        * understand why that is as the number of retries is also causing a
+                        * heavier load on AAI beyond the throttling controls we already
+                        * have in place in term of the transaction rate controller and
+                        * number of parallelized threads per task processor.
+                        */
+
+                       result.setNumRetries(retryCount);
+
+                       if (!shouldRetryRequest(result)) {
+
+                               result.setFromCache(false);
+                               LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_DONE_SEQ, url, String.valueOf(retryCount + 1));
+
+                               return result;
+                       }
+
+                       try {
+                               /*
+                                * Sleep between re-tries to be nice to the target system.
+                                */
+                               Thread.sleep(50);
+                       } catch (InterruptedException exc) {
+                               LOG.error(AaiUiMsgs.QUERY_AAI_WAIT_INTERRUPTION, exc.getLocalizedMessage());
+                               break;
+                       }
+                       LOG.error(AaiUiMsgs.QUERY_AAI_RETRY_FAILURE_WITH_SEQ, url, String.valueOf(retryCount + 1));
+
+               }
+
+               LOG.info(AaiUiMsgs.QUERY_AAI_RETRY_MAXED_OUT, url);
+
+               return result;
+
+       }
+
+       /**
+        * This method adds a scheme, host and port (if missing) to the passed-in
+        * URI. If these parts of the URI are already present, they will not be
+        * duplicated.
+        * 
+        * @param selflink
+        *            The URI to repair
+        * @param queryParams
+        *            The query parameters as a single string
+        * @return The corrected URI (i.e. includes a scheme/host/port)
+        */
+
+  private String repairGizmoSelfLink(String baseUrlPath, String selfLink, String queryParams) {
+
+    if (selfLink == null) {
+      return selfLink;
+    }
+    
+    if (selfLink.startsWith("http") || selfLink.startsWith("https")) {
+      return selfLink;
+    }
+    
+    UriBuilder builder = UriBuilder.fromPath(baseUrlPath + "/" + selfLink)
+        .host(endpointConfig.getEndpointIpAddress())
+        .port(Integer.parseInt(endpointConfig.getEndpointServerPort()));
+
+    switch (endpointConfig.getRestAuthenticationMode()) {
+
+      case SSL_BASIC:
+      case SSL_CERT: {
+        builder.scheme(HTTPS_SCHEME);
+        break;
+      }
+
+      default: {
+        builder.scheme(HTTP_SCHEME);
+      }
+    }
+
+    boolean includeQueryParams = ((null != queryParams) && (!"".equals(queryParams)));
+
+    /*
+     * builder.build().toString() will encode special characters to hexadecimal pairs prefixed with
+     * a '%' so we're adding the query parameters separately, in their UTF-8 representations, so
+     * that characters such as '?', '&', etc. remain intact as needed by the synchronizer
+     */
+    return (builder.build().toString() + (includeQueryParams ? queryParams : ""));
+
+  }
+       
+  public String repairRelationshipSelfLink(String selflink, String queryParams) {
+    return repairGizmoSelfLink(relationshipsBasePath, selflink, queryParams);
+  }
+
+  public String repairInventorySelfLink(String selflink, String queryParams) {
+    return repairGizmoSelfLink(inventoryBasePath, selflink, queryParams);
+  }
+
+       public OperationResult getSelfLinksByEntityType(String entityType) throws Exception {
+               
+               if (entityType == null) {
+                       throw new NullPointerException("Failed to getSelfLinksByEntityType() because entityType is null");
+               }
+
+               String link = getFullInventoryUrl(entityType);
+
+               return queryGizmoWithRetries(link, "application/json", endpointConfig.getNumRequestRetries());
+
+       }
+       
+       public static String extractResourcePath(String selflink) {
+               try {
+                       return new URI(selflink).getRawPath();
+               } catch (URISyntaxException uriSyntaxException) {
+                       LOG.error(AaiUiMsgs.ERROR_EXTRACTING_RESOURCE_PATH_FROM_LINK, uriSyntaxException.getMessage());
+                       return selflink;
+               }
+       }
+
+}
diff --git a/src/main/java/org/onap/aai/sparky/sync/task/PerformGizmoRetrieval.java b/src/main/java/org/onap/aai/sparky/sync/task/PerformGizmoRetrieval.java
new file mode 100644 (file)
index 0000000..374163f
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.sync.task;
+
+import java.util.Map;
+import java.util.function.Supplier;
+
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.aai.restclient.client.OperationResult;
+import org.onap.aai.sparky.dal.GizmoAdapter;
+import org.onap.aai.sparky.dal.NetworkTransaction;
+import org.onap.aai.sparky.logging.AaiUiMsgs;
+import org.slf4j.MDC;
+
+/*
+ * Consider abstraction the tasks into common elemnts, because most of them repeat a generic call
+ * flow pattern
+ */
+
+/**
+ * The Class PerformActiveInventoryRetrieval.
+ */
+public class PerformGizmoRetrieval implements Supplier<NetworkTransaction> {
+
+  private static Logger logger = LoggerFactory.getInstance().getLogger(PerformGizmoRetrieval.class);
+
+  private NetworkTransaction txn;
+  private GizmoAdapter gizmoAdapter;
+  private Map<String, String> contextMap;
+
+  /**
+   * Instantiates a new perform active inventory retrieval.
+   *
+   * @param txn the txn
+   * @param aaiProvider the aai provider
+   */
+  public PerformGizmoRetrieval(NetworkTransaction txn,
+      GizmoAdapter gizmoAdapter) {
+    this.txn = txn;
+    this.gizmoAdapter = gizmoAdapter;
+    this.contextMap = MDC.getCopyOfContextMap();
+  }
+
+  /* (non-Javadoc)
+   * @see java.util.function.Supplier#get()
+   */
+  @Override
+  public NetworkTransaction get() {
+
+    txn.setTaskAgeInMs();
+
+    long startTimeInMs = System.currentTimeMillis();
+    MDC.setContextMap(contextMap);
+    OperationResult result = null;
+    try {
+      result = gizmoAdapter.queryGizmoWithRetries(txn.getLink(), "application/json", 5);
+    } catch (Exception exc) {
+      logger.error(AaiUiMsgs.ERROR_GENERIC,"Failure to resolve self link from AAI.  Error = " + exc.getMessage());
+      result = new OperationResult(500,
+          "Caught an exception while trying to resolve link = " + exc.getMessage());
+    } finally {
+      txn.setOperationResult(result);
+      txn.setOpTimeInMs(System.currentTimeMillis() - startTimeInMs);
+    }
+
+    return txn;
+  }
+
+  protected void setContextMap(Map<String, String> contextMap) {
+    this.contextMap = contextMap;
+  }
+}
index 7657d7e..dd8e882 100644 (file)
@@ -73,7 +73,9 @@ public class NodeUtils {
   private static SecureRandom sRandom = new SecureRandom();
   
   private static final Pattern AAI_VERSION_PREFIX = Pattern.compile("/aai/v[0-9]+/(.*)");
-
+  private static final Pattern GIZMO_VERSION_PREFIX = Pattern.compile("[/]*services/inventory/v[0-9]+/(.*)");
+  private static final Pattern GIZMO_RELATIONSHIP_VERSION_PREFIX = Pattern.compile("services/inventory/relationships/v[0-9]+/(.*)");
+                                                                                    
   
   public static synchronized String getRandomTxnId(){
       byte bytes[] = new byte[6];
@@ -119,6 +121,53 @@ public class NodeUtils {
     return null;
     
   }
+  
+  public static String extractRawGizmoPathWithoutVersion(String resourceLink) {
+
+    try {
+
+      String rawPath = new URI(resourceLink).getRawPath();
+      
+      Matcher m = GIZMO_VERSION_PREFIX.matcher(rawPath);
+
+      if (m.matches()) {
+
+        if ( m.groupCount() >= 1) {
+          return m.group(1);
+        }
+          
+      }
+    } catch (Exception e) {
+    }
+    
+    return null;
+    
+  }
+  
+  public static String extractRawGizmoRelationshipPathWithoutVersion(String resourceLink) {
+
+    try {
+
+      String rawPath = new URI(resourceLink).getRawPath();
+      
+      Matcher m = GIZMO_RELATIONSHIP_VERSION_PREFIX.matcher(rawPath);
+
+      if (m.matches()) {
+
+        if ( m.groupCount() >= 1) {
+          return m.group(1);
+        }
+          
+      }
+    } catch (Exception e) {
+    }
+    
+    return null;
+    
+  }  
+  
+  
+
 
   /**
    * Checks if is numeric.
@@ -163,12 +212,7 @@ public class NodeUtils {
     return Executors.newScheduledThreadPool(numWorkers + 1, namedThreadFactory);
   }
 
-  /**
-   * Calculate edit attribute uri.
-   *
-   * @param link the link
-   * @return the string
-   */
+
   public static String calculateEditAttributeUri(String link) {
     String uri = null;
 
@@ -183,6 +227,7 @@ public class NodeUtils {
     return uri;
   }
 
+  
   /**
    * Generate unique sha digest.
    *
@@ -612,6 +657,51 @@ public class NodeUtils {
       }
 
     }
+  }
+    
+  public static String extractObjectValueByKey(JsonNode node, String searchKey) {
+
+    if (node == null) {
+      return null;
+    }
+
+    if (node.isObject()) {
+      Iterator<Map.Entry<String, JsonNode>> nodeIterator = node.fields();
+
+      while (nodeIterator.hasNext()) {
+        Map.Entry<String, JsonNode> entry = nodeIterator.next();
+        if (!entry.getValue().isValueNode()) {
+          return extractObjectValueByKey(entry.getValue(), searchKey);
+        }
+
+        String name = entry.getKey();
+        if (name.equalsIgnoreCase(searchKey)) {
+
+          JsonNode entryNode = entry.getValue();
+
+          if (entryNode.isArray()) {
+
+            Iterator<JsonNode> arrayItemsIterator = entryNode.elements();
+            while (arrayItemsIterator.hasNext()) {
+              return arrayItemsIterator.next().asText();
+            }
+
+          } else {
+            return entry.getValue().asText();
+          }
+
+
+        }
+      }
+    } else if (node.isArray()) {
+      Iterator<JsonNode> arrayItemsIterator = node.elements();
+      while (arrayItemsIterator.hasNext()) {
+        return extractObjectValueByKey(arrayItemsIterator.next(), searchKey);
+      }
+
+    }
+
+    return null;
 
   }
 
index a0be371..aff24bc 100644 (file)
@@ -46,11 +46,15 @@ public class VisualizationConfigs {
   private boolean makeAllNeighborsBidirectional;
   
   private ArrayList<String> shallowEntities;
+  
+  private boolean gizmoEnabled;
 
   /**
    * Instantiates a new visualization config.
    */
-  public VisualizationConfigs() {}
+  public VisualizationConfigs() {
+         this.gizmoEnabled = false;
+  }
 
   public ArrayList<String> getShallowEntities() {
     return shallowEntities;
@@ -129,23 +133,28 @@ public class VisualizationConfigs {
     this.numOfThreadsToFetchNodeIntegrity = numOfThreadsToFetchNodeIntegrity;
   }
 
-
-
-  @Override
-  public String toString() {
-    return "VisualizationConfigs [maxSelfLinkTraversalDepth=" + maxSelfLinkTraversalDepth
-        + ", visualizationDebugEnabled=" + visualizationDebugEnabled + ", "
-        + (aaiEntityNodeDescriptors != null
-            ? "aaiEntityNodeDescriptors=" + aaiEntityNodeDescriptors + ", " : "")
-        + (generalNodeClassName != null ? "generalNodeClassName=" + generalNodeClassName + ", "
-            : "")
-        + (searchNodeClassName != null ? "searchNodeClassName=" + searchNodeClassName + ", " : "")
-        + (selectedSearchedNodeClassName != null
-            ? "selectedSearchedNodeClassName=" + selectedSearchedNodeClassName + ", " : "")
-        + "numOfThreadsToFetchNodeIntegrity=" + numOfThreadsToFetchNodeIntegrity
-        + ", makeAllNeighborsBidirectional=" + makeAllNeighborsBidirectional + "]";
-  }
-
-
+       public boolean isGizmoEnabled() {
+               return gizmoEnabled;
+       }
+
+       public void setGizmoEnabled(boolean gizmoEnabled) {
+               this.gizmoEnabled = gizmoEnabled;
+       }
+
+       @Override
+       public String toString() {
+               return "VisualizationConfigs [maxSelfLinkTraversalDepth=" + maxSelfLinkTraversalDepth
+                               + ", visualizationDebugEnabled=" + visualizationDebugEnabled + ", "
+                               + (aaiEntityNodeDescriptors != null ? "aaiEntityNodeDescriptors=" + aaiEntityNodeDescriptors + ", "
+                                               : "")
+                               + (generalNodeClassName != null ? "generalNodeClassName=" + generalNodeClassName + ", " : "")
+                               + (searchNodeClassName != null ? "searchNodeClassName=" + searchNodeClassName + ", " : "")
+                               + (selectedSearchedNodeClassName != null
+                                               ? "selectedSearchedNodeClassName=" + selectedSearchedNodeClassName + ", " : "")
+                               + "numOfThreadsToFetchNodeIntegrity=" + numOfThreadsToFetchNodeIntegrity
+                               + ", makeAllNeighborsBidirectional=" + makeAllNeighborsBidirectional + ", "
+                               + (shallowEntities != null ? "shallowEntities=" + shallowEntities + ", " : "") + "gizmoEnabled="
+                               + gizmoEnabled + "]";
+       }
 
 }
index c5d9b4a..e90d469 100644 (file)
@@ -66,6 +66,10 @@ public class ActiveInventoryNode {
   private boolean isRootNode;
   private ConcurrentLinkedDeque<String> inboundNeighbors;
   private ConcurrentLinkedDeque<String> outboundNeighbors;
+  
+  private ConcurrentLinkedDeque<String> inboundNeighborSelfLinks;
+  private ConcurrentLinkedDeque<String> outboundNeighborSelfLinks;
+  
   private List<JsonNode> complexGroups;
   private List<RelationshipList> relationshipLists;
   private int nodeDepth;
@@ -139,6 +143,10 @@ public class ActiveInventoryNode {
     isRootNode = false;
     inboundNeighbors = new ConcurrentLinkedDeque<String>();
     outboundNeighbors = new ConcurrentLinkedDeque<String>();
+    
+    inboundNeighborSelfLinks = new ConcurrentLinkedDeque<String>();
+    outboundNeighborSelfLinks = new ConcurrentLinkedDeque<String>();
+    
     complexGroups = new ArrayList<JsonNode>();
     relationshipLists = new ArrayList<RelationshipList>();
     nodeDepth = DEFAULT_INIT_NODE_DEPTH;
@@ -164,6 +172,38 @@ public class ActiveInventoryNode {
     }
   }
   
+       public void addInboundSelfLink(String link) {
+
+               if (link == null) {
+                       return;
+               }
+
+               if (!inboundNeighborSelfLinks.contains(link)) {
+                       inboundNeighborSelfLinks.add(link);
+               }
+
+       }
+
+       public void addOutboundSelfLink(String link) {
+
+               if (link == null) {
+                       return;
+               }
+
+               if (!outboundNeighborSelfLinks.contains(link)) {
+                       outboundNeighborSelfLinks.add(link);
+               }
+
+       }
+
+       public Collection<String> getInboundNeighborSelfLinks() {
+               return inboundNeighborSelfLinks;
+       }
+
+       public Collection<String> getOutboundNeighborSelfLinks() {
+               return outboundNeighborSelfLinks;
+       }
+  
   public void addQueryParams(Collection<String> params) {
 
     if (params != null & params.size() > 0) {
diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoEntity.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoEntity.java
new file mode 100644 (file)
index 0000000..211efa5
--- /dev/null
@@ -0,0 +1,96 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.viewandinspect.entity;
+
+import java.util.Arrays;
+import java.util.Map;
+
+public class GizmoEntity {
+
+  private String id;
+  private String type;
+  private String url;
+  private Map<String, String> properties;
+  private GizmoRelationshipHint[] in;
+  private GizmoRelationshipHint[] out;
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getType() {
+    return type;
+  }
+
+  public void setType(String type) {
+    this.type = type;
+  }
+
+  public String getUrl() {
+    return url;
+  }
+
+  public void setUrl(String url) {
+    this.url = url;
+  }
+
+  public Map<String, String> getProperties() {
+    return properties;
+  }
+
+  public void setProperties(Map<String, String> properties) {
+    this.properties = properties;
+  }
+
+  public GizmoRelationshipHint[] getIn() {
+    return in;
+  }
+
+  public void setIn(GizmoRelationshipHint[] in) {
+    this.in = in;
+  }
+
+  public GizmoRelationshipHint[] getOut() {
+    return out;
+  }
+
+  public void setOut(GizmoRelationshipHint[] out) {
+    this.out = out;
+  }
+
+  @Override
+  public String toString() {
+    return "GizmoEntity [" + (id != null ? "id=" + id + ", " : "")
+        + (type != null ? "type=" + type + ", " : "") + (url != null ? "url=" + url + ", " : "")
+        + (properties != null ? "properties=" + properties + ", " : "")
+        + (in != null ? "in=" + Arrays.toString(in) + ", " : "")
+        + (out != null ? "out=" + Arrays.toString(out) : "") + "]";
+  }
+
+
+
+}
diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipEntity.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipEntity.java
new file mode 100644 (file)
index 0000000..a566d69
--- /dev/null
@@ -0,0 +1,101 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.viewandinspect.entity;
+
+import java.util.Map;
+
+public class GizmoRelationshipEntity {
+
+       /*
+        * {"id":"oe4ur-3a0-27th-fu8","type":"has","url":
+        * "services/inventory/relationships/v8/has/oe4ur-3a0-27th-fu8","source":
+        * "services/inventory/v8/generic-vnf/4248","target":
+        * "services/inventory/v8/vserver/20528",
+        * "properties":{"is-parent":"true","multiplicity":"many","has-del-target":
+        * "true","uses-resource": "true"}}
+        */
+
+       private String id;
+       private String type;
+       private String url;
+       private String source;
+       private String target;
+       private Map<String, String> properties;
+
+       public String getId() {
+               return id;
+       }
+
+       public void setId(String id) {
+               this.id = id;
+       }
+
+       public String getType() {
+               return type;
+       }
+
+       public void setType(String type) {
+               this.type = type;
+       }
+
+       public String getUrl() {
+               return url;
+       }
+
+       public void setUrl(String url) {
+               this.url = url;
+       }
+
+       public String getSource() {
+               return source;
+       }
+
+       public void setSource(String source) {
+               this.source = source;
+       }
+
+       public String getTarget() {
+               return target;
+       }
+
+       public void setTarget(String target) {
+               this.target = target;
+       }
+
+       public Map<String, String> getProperties() {
+               return properties;
+       }
+
+       public void setProperties(Map<String, String> properties) {
+               this.properties = properties;
+       }
+
+       @Override
+       public String toString() {
+               return "GizmoRelationshipEntity [" + (id != null ? "id=" + id + ", " : "")
+                               + (type != null ? "type=" + type + ", " : "") + (url != null ? "url=" + url + ", " : "")
+                               + (source != null ? "source=" + source + ", " : "") + (target != null ? "target=" + target + ", " : "")
+                               + (properties != null ? "properties=" + properties : "") + "]";
+       }
+
+}
diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipHint.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipHint.java
new file mode 100644 (file)
index 0000000..f28ba5b
--- /dev/null
@@ -0,0 +1,75 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.viewandinspect.entity;
+
+public class GizmoRelationshipHint {
+
+         private String id;
+         private String type;
+         private String url;
+         private String source;
+         private String target;
+
+         public String getId() {
+           return id;
+         }
+
+         public void setId(String id) {
+           this.id = id;
+         }
+
+         public String getType() {
+           return type;
+         }
+
+         public void setType(String type) {
+           this.type = type;
+         }
+
+         public String getUrl() {
+           return url;
+         }
+
+         public void setUrl(String url) {
+           this.url = url;
+         }
+
+         public String getSource() {
+           return source;
+         }
+
+         public void setSource(String source) {
+           this.source = source;
+         }
+
+         public String getTarget() {
+           return target;
+         }
+
+         public void setTarget(String target) {
+           this.target = target;
+         }
+
+
+
+       }
diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseGizmoVisualizationContext.java b/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseGizmoVisualizationContext.java
new file mode 100644 (file)
index 0000000..8963ff7
--- /dev/null
@@ -0,0 +1,988 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.viewandinspect.services;
+
+import static java.util.concurrent.CompletableFuture.supplyAsync;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.aai.restclient.client.OperationResult;
+import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
+import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
+import org.onap.aai.sparky.dal.GizmoAdapter;
+import org.onap.aai.sparky.logging.AaiUiMsgs;
+import org.onap.aai.sparky.sync.entity.SearchableEntity;
+import org.onap.aai.sparky.util.NodeUtils;
+import org.onap.aai.sparky.viewandinspect.config.SparkyConstants;
+import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
+import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
+import org.onap.aai.sparky.viewandinspect.entity.GizmoEntity;
+import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipEntity;
+import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipHint;
+import org.onap.aai.sparky.viewandinspect.entity.InlineMessage;
+import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction;
+import org.onap.aai.sparky.viewandinspect.entity.QueryParams;
+import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction;
+import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState;
+import org.onap.aai.sparky.viewandinspect.task.PerformGizmoNodeSelfLinkProcessingTask;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+
+/**
+ * The Class SelfLinkNodeCollector.
+ */
+public class BaseGizmoVisualizationContext implements VisualizationContext {
+
+  private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100;
+
+  private static final Logger LOG =
+      LoggerFactory.getInstance().getLogger(BaseGizmoVisualizationContext.class);
+
+  private final GizmoAdapter gizmoAdapter;
+
+  private AtomicInteger numLinksDiscovered;
+  private AtomicInteger numSuccessfulLinkResolveFromCache;
+  private AtomicInteger numSuccessfulLinkResolveFromFromServer;
+  private AtomicInteger numFailedLinkResolve;
+  private AtomicInteger aaiWorkOnHand;
+
+  private VisualizationConfigs visualizationConfigs;
+
+  private AtomicInteger totalLinksRetrieved;
+
+  private final long contextId;
+  private final String contextIdStr;
+  private long lastProcessStatesSummaryLogInMs = -1;
+
+
+  private ObjectMapper mapper;
+  private InlineMessage inlineMessage = null;
+
+  private ExecutorService graphExecutorService;
+  private OxmEntityLookup oxmEntityLookup;
+  private boolean rootNodeFound;
+
+  /*
+   * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly
+   * re-requesting the same self-links over-and-over again, to speed up the overall render time and
+   * more importantly to reduce the network cost of determining information we already have.
+   */
+  private ConcurrentHashMap<String, ActiveInventoryNode> nodeCache;
+
+  /**
+   * Instantiates a new self link node collector.
+   *
+   * @param loader the loader
+   * @throws Exception the exception
+   */
+  public BaseGizmoVisualizationContext(long contextId, GizmoAdapter gizmoAdapter,
+      ExecutorService graphExecutorService, VisualizationConfigs visualizationConfigs,
+      OxmEntityLookup oxmEntityLookup) throws Exception {
+
+    this.contextId = contextId;
+    this.contextIdStr = "[Context-Id=" + contextId + "]";
+    this.gizmoAdapter = gizmoAdapter;
+    this.graphExecutorService = graphExecutorService;
+    this.visualizationConfigs = visualizationConfigs;
+    this.oxmEntityLookup = oxmEntityLookup;
+
+    this.nodeCache = new ConcurrentHashMap<String, ActiveInventoryNode>();
+    this.numLinksDiscovered = new AtomicInteger(0);
+    this.totalLinksRetrieved = new AtomicInteger(0);
+    this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0);
+    this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0);
+    this.numFailedLinkResolve = new AtomicInteger(0);
+    this.aaiWorkOnHand = new AtomicInteger(0);
+
+    this.mapper = new ObjectMapper();
+    mapper.setSerializationInclusion(Include.NON_EMPTY);
+    mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy());
+    this.rootNodeFound = false;
+  }
+
+  protected boolean isRootNodeFound() {
+    return rootNodeFound;
+  }
+
+  protected void setRootNodeFound(boolean rootNodeFound) {
+    this.rootNodeFound = rootNodeFound;
+  }
+
+  public long getContextId() {
+    return contextId;
+  }
+
+  public GizmoAdapter getGizmoAdapter() {
+    return gizmoAdapter;
+  }
+
+  /**
+   * Process self link response.
+   *
+   * @param nodeId the node id
+   */
+  private void processSelfLinkResponse(String nodeId) {
+
+    if (nodeId == null) {
+      LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
+          "Cannot process self link" + " response because nodeId is null");
+      return;
+    }
+
+    ActiveInventoryNode ain = nodeCache.get(nodeId);
+
+    if (ain == null) {
+      LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
+          "Cannot process self link response" + " because can't find node for id = " + nodeId);
+      return;
+    }
+
+    GizmoEntity gizmoEntity = null;
+
+    try {
+      gizmoEntity = mapper.readValue(ain.getOpResult().getResult(), GizmoEntity.class);
+    } catch (Exception exc) {
+      exc.printStackTrace();
+      LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json"
+          + " response str into JsonNode with error, " + exc.getLocalizedMessage());
+      ain.changeState(NodeProcessingState.ERROR,
+          NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
+      return;
+    }
+
+    if (gizmoEntity == null) {
+
+      LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR,
+          "Failed to parse json node str." + " Parse resulted a null value.");
+      ain.changeState(NodeProcessingState.ERROR,
+          NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR);
+      return;
+    }
+
+    /*
+     * Now that we have the gizmo entity we can populate the AIN node with it, as well as the
+     * relationships
+     */
+
+    ain.setEntityType(gizmoEntity.getType());
+
+    ain.setPrimaryKeyName(getEntityTypePrimaryKeyName(gizmoEntity.getType()));
+    
+    OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(gizmoEntity);
+
+    if (descriptor != null) {
+      ain.setPrimaryKeyValue(getPrimaryKeyValues(gizmoEntity.getProperties(),
+          descriptor.getPrimaryKeyAttributeNames()));
+    } else {
+      LOG.error(AaiUiMsgs.ERROR_GENERIC, "Could not determine oxm descriptor for entity type = " + gizmoEntity.getType());
+    }
+    
+    gizmoEntity.getProperties().forEach((key, value) -> {
+      ain.getProperties().put(key, value);
+    });
+
+    // add edit attributes link
+    if (ain.getSelfLink() != null) {
+      ain.addProperty(SparkyConstants.URI_ATTR_NAME, ain.getSelfLink());
+    }
+    
+
+
+    /*
+     * Only discover neighbors if our depth is less than the Max-Traversal-Depth
+     */
+
+    if (ain.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) {
+
+      /*
+       * I think the next thing to do is:
+       * 
+       * 1. Calculate the source / target node id 2. Add the nodeId to the incoming / outgoing links
+       * collection 3. Add the node to the node cache for processing
+       */
+
+      String resourceLink = null;
+      String relationshipNodeId = null;
+      ActiveInventoryNode relationshipNode = null;
+
+      for (GizmoRelationshipHint inRelationship : gizmoEntity.getIn()) {
+
+        if (inRelationship.getSource() != null) {
+
+          resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(inRelationship.getSource());
+          relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink);
+
+          if (!nodeCache.containsKey(relationshipNodeId)) {
+
+            relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup);
+            relationshipNode.setNodeId(relationshipNodeId);
+            relationshipNode.setSelfLink(resourceLink);
+            relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
+                NodeProcessingAction.NEW_NODE_PROCESSED);
+
+            ain.addInboundNeighbor(relationshipNodeId);
+
+            addNode(relationshipNode);
+
+          }
+        }
+
+      }
+
+      for (GizmoRelationshipHint outRelationship : gizmoEntity.getOut()) {
+
+        if (outRelationship.getTarget() != null) {
+
+          resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(outRelationship.getTarget());
+          relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink);
+
+          if (!nodeCache.containsKey(relationshipNodeId)) {
+
+            relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup);
+            relationshipNode.setNodeId(relationshipNodeId);
+            relationshipNode.setSelfLink(resourceLink);
+            relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
+                NodeProcessingAction.NEW_NODE_PROCESSED);
+
+            ain.addOutboundNeighbor(relationshipNodeId);
+
+            addNode(relationshipNode);
+
+          }
+        }
+
+      }
+    }
+
+    ain.changeState(NodeProcessingState.READY, NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
+
+  }
+
+  /**
+   * Perform self link resolve.
+   *
+   * @param nodeId the node id
+   */
+  private void performSelfLinkResolve(String nodeId) {
+
+    if (nodeId == null) {
+      LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
+          "Resolve of self-link" + " has been skipped because provided nodeId is null");
+      return;
+    }
+
+    ActiveInventoryNode ain = nodeCache.get(nodeId);
+
+    if (ain == null) {
+      LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId
+          + ", from node cache. Resolve self-link method has been skipped.");
+      return;
+    }
+
+    if (!ain.isSelfLinkPendingResolve()) {
+
+      ain.setSelfLinkPendingResolve(true);
+
+      // kick off async self-link resolution
+
+      if (LOG.isDebugEnabled()) {
+        LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
+            "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink());
+      }
+
+      numLinksDiscovered.incrementAndGet();
+
+      /*
+       * If the current node is the search target, we want to see everything the node has to offer
+       * from the self-link and not filter it to a single node.
+       */
+
+      NodeProcessingTransaction txn = new NodeProcessingTransaction();
+      txn.setProcessingNode(ain);
+      txn.setRequestParameters(null);
+      aaiWorkOnHand.incrementAndGet();
+      supplyAsync(new PerformGizmoNodeSelfLinkProcessingTask(txn, null, gizmoAdapter),
+          graphExecutorService).whenComplete((nodeTxn, error) -> {
+
+            if (error != null) {
+
+              /*
+               * an error processing the self link should probably result in the node processing
+               * state shifting to ERROR
+               */
+
+              nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
+
+              nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
+                  NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
+
+              nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
+
+            } else {
+
+              totalLinksRetrieved.incrementAndGet();
+
+              OperationResult opResult = nodeTxn.getOpResult();
+
+              if (opResult != null && opResult.wasSuccessful()) {
+
+                if (!opResult.wasSuccessful()) {
+                  numFailedLinkResolve.incrementAndGet();
+                }
+
+                if (opResult.isFromCache()) {
+                  numSuccessfulLinkResolveFromCache.incrementAndGet();
+                } else {
+                  numSuccessfulLinkResolveFromFromServer.incrementAndGet();
+                }
+
+                // success path
+                nodeTxn.getProcessingNode().setOpResult(opResult);
+                nodeTxn.getProcessingNode().changeState(
+                    NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
+                    NodeProcessingAction.SELF_LINK_RESOLVE_OK);
+
+                nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
+                nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
+
+              } else {
+                LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
+                    "Self Link retrieval for link," + txn.getSelfLinkWithModifiers()
+                        + ", failed with error code," + nodeTxn.getOpResult().getResultCode()
+                        + ", and message," + nodeTxn.getOpResult().getResult());
+
+                nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true);
+                nodeTxn.getProcessingNode().setSelfLinkProcessed(true);
+
+                nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR,
+                    NodeProcessingAction.SELF_LINK_RESOLVE_ERROR);
+
+                nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false);
+
+              }
+            }
+
+            aaiWorkOnHand.decrementAndGet();
+
+          });
+
+    }
+
+  }
+
+  public GizmoRelationshipEntity getGizmoRelationshipEntity(String gizmoJsonResponse) {
+
+    GizmoRelationshipEntity gizmoRelationship = null;
+    try {
+      gizmoRelationship = mapper.readValue(gizmoJsonResponse, GizmoRelationshipEntity.class);
+    } catch (IOException exc) {
+      LOG.error(AaiUiMsgs.ERROR_GENERIC, "Failed to map json to GizmoRelationshipEntity.  Error: " + exc.getMessage());
+    }
+
+    return gizmoRelationship;
+
+  }
+
+  public String getPrimaryKeyValues(Map<String, String> properties, List<String> pkeyNames) {
+
+    StringBuilder sb = new StringBuilder(64);
+
+    if (pkeyNames.size() > 0) {
+      String primaryKey = properties.get(pkeyNames.get(0));
+      if (primaryKey != null) {
+        sb.append(primaryKey);
+      } else {
+        // this should be a fatal error because unless we can
+        // successfully retrieve all the expected keys we'll end up
+        // with a garbage node
+        LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract" + " keyName, "
+            + pkeyNames.get(0) + ", from properties , " + properties);
+        return null;
+      }
+
+      for (int i = 1; i < pkeyNames.size(); i++) {
+
+        String kv = properties.get(pkeyNames.get(i));
+        if (kv != null) {
+          sb.append("/").append(kv);
+        } else {
+          // this should be a fatal error because unless we can
+          // successfully retrieve all the expected keys we'll end up
+          // with a garbage node
+          LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR:  failed to extract keyName, "
+              + pkeyNames.get(i) + ", from properties, " + properties);
+          return null;
+        }
+      }
+
+      return sb.toString();
+
+    }
+
+    return null;
+
+  }
+
+
+
+  /**
+   * Find and mark root node.
+   *
+   * @param queryParams the query params
+   * @return true, if successful
+   */
+  private void findAndMarkRootNode(QueryParams queryParams) {
+
+    if (isRootNodeFound()) {
+      return;
+    }
+
+    for (ActiveInventoryNode cacheNode : nodeCache.values()) {
+
+      if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) {
+        cacheNode.setNodeDepth(0);
+        cacheNode.setRootNode(true);
+        LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
+        setRootNodeFound(true);
+      }
+    }
+
+  }
+
+  public void addNode(ActiveInventoryNode node) {
+
+    if (node == null) {
+      return;
+    }
+
+    nodeCache.putIfAbsent(node.getNodeId(), node);
+  }
+
+  public VisualizationConfigs getVisualizationConfigs() {
+    return visualizationConfigs;
+  }
+
+  public void setVisualizationConfigs(VisualizationConfigs visualizationConfigs) {
+    this.visualizationConfigs = visualizationConfigs;
+  }
+
+  public OxmEntityLookup getOxmEntityLookup() {
+    return oxmEntityLookup;
+  }
+
+  public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) {
+    this.oxmEntityLookup = oxmEntityLookup;
+  }
+
+  public ObjectMapper getMapper() {
+    return mapper;
+  }
+
+  public void setMapper(ObjectMapper mapper) {
+    this.mapper = mapper;
+  }
+
+  private void dumpThrottledWorkOnHandLog() {
+    dumpThrottledWorkOnHandLog(false);
+  }
+
+  private void dumpThrottledWorkOnHandLog(boolean override) {
+
+    if ((lastProcessStatesSummaryLogInMs < 0)
+        || ((System.currentTimeMillis() > (lastProcessStatesSummaryLogInMs + 5000))) || override) {
+
+      lastProcessStatesSummaryLogInMs = System.currentTimeMillis();
+
+      int numInit = 0;
+      int numReady = 0;
+      int numError = 0;
+      int numSelfLinkUnresolved = 0;
+      int numSelfLinkResponseUnprocessed = 0;
+
+      for (ActiveInventoryNode cacheNode : nodeCache.values()) {
+
+        switch (cacheNode.getState()) {
+
+          case INIT: {
+            numInit++;
+            break;
+          }
+
+          case READY: {
+            numReady++;
+            break;
+          }
+          case ERROR: {
+            numError++;
+            break;
+          }
+
+          case SELF_LINK_UNRESOLVED: {
+            numSelfLinkUnresolved++;
+            break;
+          }
+
+          case SELF_LINK_RESPONSE_UNPROCESSED: {
+            numSelfLinkResponseUnprocessed++;
+            break;
+          }
+
+          default:
+            break;
+        }
+
+      }
+
+      LOG.info(AaiUiMsgs.INFO_GENERIC,
+          String.format(
+              "ProcessCurrentStates for ContextId=%s, [PendingTxns=%d, numInit=%d, numSelfLinkUnresolved=%d, numSelfLinkResponseUnProcessed=%d, numReady=%d, numError=%d]",
+              contextIdStr, aaiWorkOnHand.get(), numInit, numSelfLinkUnresolved, numSelfLinkResponseUnprocessed,
+              numReady, numError));
+    }
+
+  }
+
+  /**
+   * Process current node states.
+   *
+   * @param rootNodeDiscovered the root node discovered
+   */
+  private void processCurrentNodeStates(QueryParams queryParams) {
+    /*
+     * Force an evaluation of node depths before determining if we should limit state-based
+     * traversal or processing.
+     */
+
+    findAndMarkRootNode(queryParams);
+
+    verifyOutboundNeighbors();
+
+    for (ActiveInventoryNode cacheNode : nodeCache.values()) {
+
+      if (LOG.isDebugEnabled()) {
+        LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "processCurrentNodeState(), nid = "
+            + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth());
+      }
+
+      switch (cacheNode.getState()) {
+
+        case INIT: {
+          processInitialState(cacheNode.getNodeId());
+          break;
+        }
+
+        case READY: 
+        case ERROR: {
+          break;
+        }
+
+        case SELF_LINK_UNRESOLVED: {
+          performSelfLinkResolve(cacheNode.getNodeId());
+          break;
+        }
+
+        case SELF_LINK_RESPONSE_UNPROCESSED: {
+          processSelfLinkResponse(cacheNode.getNodeId());
+          break;
+        }
+
+        default:
+          break;
+      }
+
+    }
+    
+    dumpThrottledWorkOnHandLog();
+
+  }
+
+
+
+  public int getNumSuccessfulLinkResolveFromCache() {
+    return numSuccessfulLinkResolveFromCache.get();
+  }
+
+  public int getNumSuccessfulLinkResolveFromFromServer() {
+    return numSuccessfulLinkResolveFromFromServer.get();
+  }
+
+  public int getNumFailedLinkResolve() {
+    return numFailedLinkResolve.get();
+  }
+
+  public InlineMessage getInlineMessage() {
+    return inlineMessage;
+  }
+
+  public void setInlineMessage(InlineMessage inlineMessage) {
+    this.inlineMessage = inlineMessage;
+  }
+
+  public ConcurrentHashMap<String, ActiveInventoryNode> getNodeCache() {
+    return nodeCache;
+  }
+
+
+
+  /**
+   * Process initial state.
+   *
+   * @param nodeId the node id
+   */
+  private void processInitialState(String nodeId) {
+
+    if (nodeId == null) {
+      LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null");
+      return;
+    }
+
+    ActiveInventoryNode cachedNode = nodeCache.get(nodeId);
+
+    if (cachedNode == null) {
+      LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE,
+          "Node cannot be" + " found for nodeId, " + nodeId);
+      return;
+    }
+
+    if (cachedNode.getSelfLink() == null) {
+
+      if (cachedNode.getNodeId() == null) {
+
+        /*
+         * if the self link is null at the INIT state, which could be valid if this node is a
+         * complex attribute group which didn't originate from a self-link, but in that situation
+         * both the node id and node key should already be set.
+         */
+
+        cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR);
+
+      }
+
+      if (cachedNode.getNodeId() != null) {
+
+        /*
+         * This should be the success path branch if the self-link is not set
+         */
+
+        cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED,
+            NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK);
+
+      }
+
+    } else {
+
+      if (cachedNode.hasResolvedSelfLink()) {
+        LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT);
+        cachedNode.changeState(NodeProcessingState.ERROR,
+            NodeProcessingAction.UNEXPECTED_STATE_TRANSITION);
+      } else {
+        cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED,
+            NodeProcessingAction.SELF_LINK_SET);
+      }
+    }
+  }
+
+  /**
+   * Process skeleton node.
+   *
+   * @param skeletonNode the skeleton node
+   * @param queryParams the query params
+   */
+  private void processSearchableEntity(SearchableEntity searchTargetEntity,
+      QueryParams queryParams) {
+
+    if (searchTargetEntity == null) {
+      return;
+    }
+
+    if (searchTargetEntity.getId() == null) {
+      LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton"
+          + " node because nodeId is null for node, " + searchTargetEntity.getLink());
+      return;
+    }
+
+    ActiveInventoryNode newNode =
+        new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup);
+
+    newNode.setNodeId(searchTargetEntity.getId());
+
+    newNode.setNodeDepth(0);
+    newNode.setRootNode(true);
+    LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId());
+    setRootNodeFound(true);
+
+    newNode.setSelfLink(searchTargetEntity.getLink());
+
+    nodeCache.putIfAbsent(newNode.getNodeId(), newNode);
+  }
+
+  private int getTotalWorkOnHand() {
+
+    int numNodesWithPendingStates = 0;
+
+    if (isRootNodeFound()) {
+      evaluateNodeDepths();
+    }
+
+    for (ActiveInventoryNode n : nodeCache.values()) {
+
+      switch (n.getState()) {
+
+        case READY:
+        case ERROR: {
+          // do nothing, these are our normal
+          // exit states
+          break;
+        }
+
+        default: {
+
+          /*
+           * for all other states, there is work to be done
+           */
+          numNodesWithPendingStates++;
+        }
+
+      }
+
+    }
+    
+    return (aaiWorkOnHand.get() + numNodesWithPendingStates);
+
+  }
+
+  /**
+   * Checks for out standing work.
+   *
+   * @return true, if successful
+   */
+  private void processOutstandingWork(QueryParams queryParams) {
+
+    while (getTotalWorkOnHand() > 0) {
+
+      /*
+       * Force an evaluation of node depths before determining if we should limit state-based
+       * traversal or processing.
+       */
+
+      processCurrentNodeStates(queryParams);
+
+      try {
+        Thread.sleep(10);
+      } catch (InterruptedException exc) {
+        LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage());
+        return;
+      }
+
+    }
+    
+    dumpThrottledWorkOnHandLog(true);
+
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see
+   * org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai.
+   * sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams)
+   */
+  @Override
+  public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) {
+
+    try {
+
+
+      if (searchtargetEntity == null) {
+        LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR,
+            contextIdStr + " - Failed to" + " processSelfLinks, searchtargetEntity is null");
+        return;
+      }
+
+      long startTimeInMs = System.currentTimeMillis();
+
+      processSearchableEntity(searchtargetEntity, queryParams);
+
+      /*
+       * This method is blocking until we decouple it with a CountDownLatch await condition, and
+       * make the internal graph processing more event-y.
+       */
+
+      processOutstandingWork(queryParams);
+
+      long totalResolveTime = (System.currentTimeMillis() - startTimeInMs);
+
+      long opTime = System.currentTimeMillis() - startTimeInMs;
+
+      LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime),
+          String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime));
+
+    } catch (Exception exc) {
+      LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage());
+    }
+
+  }
+
+  /**
+   * Verify outbound neighbors.
+   */
+  private void verifyOutboundNeighbors() {
+
+    for (ActiveInventoryNode srcNode : nodeCache.values()) {
+
+      for (String targetNodeId : srcNode.getOutboundNeighbors()) {
+
+        ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
+
+        if (targetNode != null && srcNode.getNodeId() != null) {
+
+          targetNode.addInboundNeighbor(srcNode.getNodeId());
+
+          if (this.visualizationConfigs.makeAllNeighborsBidirectional()) {
+            targetNode.addOutboundNeighbor(srcNode.getNodeId());
+          }
+
+        }
+
+      }
+
+    }
+
+  }
+
+  /**
+   * Evaluate node depths.
+   */
+  private void evaluateNodeDepths() {
+
+    int numChanged = -1;
+    int numAttempts = 0;
+
+    while (numChanged != 0) {
+
+      numChanged = 0;
+      numAttempts++;
+
+      for (ActiveInventoryNode srcNode : nodeCache.values()) {
+
+        if (srcNode.getState() == NodeProcessingState.INIT) {
+
+          /*
+           * this maybe the only state that we don't want to to process the node depth on, because
+           * typically it won't have any valid fields set, and it may remain in a partial state
+           * until we have processed the self-link.
+           */
+
+          continue;
+
+        }
+
+        for (String targetNodeId : srcNode.getOutboundNeighbors()) {
+          ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
+
+          if (targetNode != null) {
+
+            if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
+              numChanged++;
+            }
+          }
+        }
+
+        for (String targetNodeId : srcNode.getInboundNeighbors()) {
+          ActiveInventoryNode targetNode = nodeCache.get(targetNodeId);
+
+          if (targetNode != null) {
+
+            if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) {
+              numChanged++;
+            }
+          }
+        }
+      }
+
+      if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) {
+        LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED);
+        return;
+      }
+
+    }
+
+    if (LOG.isDebugEnabled()) {
+      if (numAttempts > 0) {
+        LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
+            "Evaluate node depths completed in " + numAttempts + " attempts");
+      } else {
+        LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
+            "Evaluate node depths completed in 0 attempts because all nodes at correct depth");
+      }
+    }
+
+  }
+
+
+  /**
+   * Gets the entity type primary key name.
+   *
+   * @param entityType the entity type
+   * @return the entity type primary key name
+   */
+
+
+  private String getEntityTypePrimaryKeyName(String entityType) {
+
+    if (entityType == null) {
+      LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
+          "node primary key" + " name because entity type is null");
+      return null;
+    }
+
+    OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
+
+    if (descriptor == null) {
+      LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
+          "oxm entity" + " descriptor for entityType = " + entityType);
+      return null;
+    }
+
+    List<String> pkeyNames = descriptor.getPrimaryKeyAttributeNames();
+
+    if (pkeyNames == null || pkeyNames.size() == 0) {
+      LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE,
+          "node primary" + " key because descriptor primary key names is empty");
+      return null;
+    }
+
+    return NodeUtils.concatArray(pkeyNames, "/");
+
+  }
+
+}
index ed5ec8d..9c3c7da 100644 (file)
@@ -37,6 +37,7 @@ import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
 import org.onap.aai.sparky.config.oxm.OxmModelLoader;
 import org.onap.aai.sparky.dal.ActiveInventoryAdapter;
 import org.onap.aai.sparky.dal.ElasticSearchAdapter;
+import org.onap.aai.sparky.dal.GizmoAdapter;
 import org.onap.aai.sparky.logging.AaiUiMsgs;
 import org.onap.aai.sparky.subscription.config.SubscriptionConfig;
 import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig;
@@ -64,6 +65,7 @@ public class BaseVisualizationService implements VisualizationService {
   private ObjectMapper mapper = new ObjectMapper();
 
   private final ActiveInventoryAdapter aaiAdapter;
+  private final GizmoAdapter gizmoAdapter;
   private final ElasticSearchAdapter esAdapter;
   private final ExecutorService aaiExecutorService;
   
@@ -76,16 +78,18 @@ public class BaseVisualizationService implements VisualizationService {
   private ElasticSearchSchemaConfig schemaEConfig;
   private OxmEntityLookup oxmEntityLookup;
   
-  public BaseVisualizationService(OxmModelLoader loader, VisualizationConfigs visualizationConfigs,
-                 ActiveInventoryAdapter aaiAdapter,ElasticSearchAdapter esAdapter,
-                 ElasticSearchEndpointConfig endpointConfig, ElasticSearchSchemaConfig schemaConfig, int numActiveInventoryWorkers, 
-                 OxmEntityLookup oxmEntityLookup, SubscriptionConfig subscriptionConfig) throws Exception {
+       public BaseVisualizationService(OxmModelLoader loader, VisualizationConfigs visualizationConfigs,
+                       ActiveInventoryAdapter aaiAdapter, GizmoAdapter gizmoAdapter, ElasticSearchAdapter esAdapter,
+                       ElasticSearchEndpointConfig endpointConfig, ElasticSearchSchemaConfig schemaConfig,
+                       int numActiveInventoryWorkers, OxmEntityLookup oxmEntityLookup, SubscriptionConfig subscriptionConfig)
+                       throws Exception {
    
     this.visualizationConfigs = visualizationConfigs;
     this.endpointEConfig = endpointConfig; 
     this.schemaEConfig = schemaConfig; 
     this.oxmEntityLookup = oxmEntityLookup;
     this.subConfig = subscriptionConfig;
+    
 
     secureRandom = new SecureRandom();
     
@@ -94,6 +98,7 @@ public class BaseVisualizationService implements VisualizationService {
      */
  
     this.aaiAdapter = aaiAdapter;
+    this.gizmoAdapter = gizmoAdapter;
     this.esAdapter = esAdapter; 
 
     this.mapper = new ObjectMapper();
@@ -276,8 +281,14 @@ public class BaseVisualizationService implements VisualizationService {
     VisualizationContext visContext = null;
     long contextId = secureRandom.nextLong();
     try {
-      visContext = new BaseVisualizationContext(contextId, this.aaiAdapter, aaiExecutorService,
-          this.visualizationConfigs, oxmEntityLookup);
+       if ( visualizationConfigs.isGizmoEnabled()) {
+             visContext = new BaseGizmoVisualizationContext(contextId, this.gizmoAdapter, aaiExecutorService,
+                     this.visualizationConfigs, oxmEntityLookup);
+       } else {
+             visContext = new BaseVisualizationContext(contextId, this.aaiAdapter, aaiExecutorService,
+                     this.visualizationConfigs, oxmEntityLookup);
+       }
+       
       contextMap.putIfAbsent(contextId, visContext);
     } catch (Exception e1) {
       LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT,
diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformGizmoNodeSelfLinkProcessingTask.java b/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformGizmoNodeSelfLinkProcessingTask.java
new file mode 100644 (file)
index 0000000..bccd7aa
--- /dev/null
@@ -0,0 +1,126 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.viewandinspect.task;
+
+import java.util.Map;
+import java.util.function.Supplier;
+
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.aai.restclient.client.OperationResult;
+import org.onap.aai.sparky.dal.GizmoAdapter;
+import org.onap.aai.sparky.logging.AaiUiMsgs;
+import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction;
+import org.slf4j.MDC;
+
+/**
+ * The Class PerformNodeSelfLinkProcessingTask.
+ */
+public class PerformGizmoNodeSelfLinkProcessingTask implements Supplier<NodeProcessingTransaction> {
+
+  private static final Logger logger =
+      LoggerFactory.getInstance().getLogger(PerformGizmoNodeSelfLinkProcessingTask.class);
+
+  private NodeProcessingTransaction txn;
+  private GizmoAdapter gizmoAdapter;
+  private Map<String, String> contextMap;
+
+  /**
+   * Instantiates a new perform node self link processing task.
+   *
+   * @param txn the txn
+   * @param aaiProvider the aai provider
+   * @param aaiConfig the aai config
+   */
+  /**
+   * 
+   * @param txn
+   * @param requestParameters
+   * @param aaiProvider
+   * @param aaiConfig
+   */
+  public PerformGizmoNodeSelfLinkProcessingTask(NodeProcessingTransaction txn, String requestParameters,
+      GizmoAdapter gizmoAdapter) {
+    this.gizmoAdapter = gizmoAdapter;
+    this.txn = txn;
+    this.contextMap = MDC.getCopyOfContextMap();
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see java.util.function.Supplier#get()
+   */
+  @Override
+  public NodeProcessingTransaction get() {
+    MDC.setContextMap(contextMap);
+    OperationResult opResult = new OperationResult();
+    String link = txn.getSelfLink();
+    
+    if (link == null) {
+      opResult.setResult(500, "Aborting self-link processing because self link is null");
+      txn.setOpResult(opResult);
+      return txn;
+    }
+
+    /**
+     * Rebuild the self link:
+     *  
+     * <li>build the base url with the configured scheme + authority (server:port)
+     * <li>recombine baseUrl + originalEncodedLink + queryStringParameters
+     * 
+     */
+
+    final String urlSchemeAndAuthority = gizmoAdapter.repairInventorySelfLink("", null);
+
+    String parameters = txn.getRequestParameters();
+    link = urlSchemeAndAuthority + link;
+    
+    if (parameters != null) {
+      link += parameters;
+    }
+
+    if (logger.isDebugEnabled()) {
+      logger.debug(AaiUiMsgs.DEBUG_GENERIC, "Collecting " + link);
+    }
+
+    try {
+       
+      opResult = gizmoAdapter.queryGizmoWithRetries(link, "application/json",
+          gizmoAdapter.getEndpointConfig().getNumRequestRetries());
+    } catch (Exception exc) {
+      opResult = new OperationResult();
+      opResult.setResult(500, "Querying AAI with retry failed due to an exception.");
+      logger.error(AaiUiMsgs.ERROR_AAI_QUERY_WITH_RETRY, exc.getMessage());
+    }
+
+    if (logger.isDebugEnabled()) {
+      logger.debug(AaiUiMsgs.DEBUG_GENERIC, "Operation result = " + opResult.toString());
+    }
+
+    txn.setOpResult(opResult);
+    return txn;
+
+  }
+
+}
diff --git a/src/test/java/org/onap/aai/sparky/config/oxm/OxmEntityContainerLookup.java b/src/test/java/org/onap/aai/sparky/config/oxm/OxmEntityContainerLookup.java
new file mode 100644 (file)
index 0000000..7d55e4d
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.config.oxm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.persistence.dynamic.DynamicType;
+import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
+import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
+
+public class OxmEntityContainerLookup implements OxmModelProcessor {
+
+  private Collection<String> searchableEntityGroups;
+  private Collection<String> entityContainers;
+
+  public OxmEntityContainerLookup() {
+    searchableEntityGroups = new ArrayList<String>();
+    entityContainers = new ArrayList<String>();
+  }
+
+  @Override
+  public void processOxmModel(DynamicJAXBContext jaxbContext) {
+
+    @SuppressWarnings("rawtypes")
+    List<Descriptor> descriptorsList = jaxbContext.getXMLContext().getDescriptors();
+
+    for (@SuppressWarnings("rawtypes")
+    Descriptor desc : descriptorsList) {
+
+      DynamicType entity = jaxbContext.getDynamicType(desc.getAlias());
+
+      @SuppressWarnings("unchecked")
+      Map<String, String> properties = entity.getDescriptor().getProperties();
+
+      if (properties != null) {
+
+        String container = properties.get("container");
+
+        if (container != null && !entityContainers.contains(container)) {
+
+          entityContainers.add(container);
+
+          if (properties.containsKey("searchable")) {
+            if (!searchableEntityGroups.contains(container)) {
+              searchableEntityGroups.add(container);
+            }
+          }
+        }
+
+      }
+
+    }
+
+  }
+
+  public Collection<String> getSearchableEntityGroups() {
+    return searchableEntityGroups;
+  }
+
+  public void setSearchableEntityGroups(Collection<String> searchableEntityGroups) {
+    this.searchableEntityGroups = searchableEntityGroups;
+  }
+
+  public Collection<String> getEntityContainers() {
+    return entityContainers;
+  }
+
+  public void setEntityContainers(Collection<String> entityContainers) {
+    this.entityContainers = entityContainers;
+  }
+
+  public boolean isEntityContainer(String entityType) {
+    return entityContainers.contains(entityType);
+  }
+
+}
diff --git a/src/test/java/org/onap/aai/sparky/synchronizer/GizmoEntitySummarizer.java b/src/test/java/org/onap/aai/sparky/synchronizer/GizmoEntitySummarizer.java
new file mode 100644 (file)
index 0000000..5ea5280
--- /dev/null
@@ -0,0 +1,251 @@
+package org.onap.aai.sparky.synchronizer;
+
+import static java.util.concurrent.CompletableFuture.supplyAsync;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
+
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.aai.restclient.client.OperationResult;
+import org.onap.aai.restclient.enums.RestAuthenticationMode;
+import org.onap.aai.sparky.config.oxm.OxmModelLoader;
+import org.onap.aai.sparky.dal.GizmoAdapter;
+import org.onap.aai.sparky.dal.exception.ElasticSearchOperationException;
+import org.onap.aai.sparky.dal.rest.RestClientConstructionException;
+import org.onap.aai.sparky.dal.rest.config.RestEndpointConfig;
+import org.onap.aai.sparky.logging.AaiUiMsgs;
+import org.onap.aai.sparky.util.NodeUtils;
+import org.onap.aai.sparky.util.OxmModelAndProcessorHelper;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+public class GizmoEntitySummarizer {
+
+       protected ObjectMapper mapper;
+       protected OxmModelLoader oxmModelLoader;
+       private static final Logger logger = LoggerFactory.getInstance().getLogger(GizmoEntitySummarizer.class);
+       protected ExecutorService gizmoExecutor;
+       protected GizmoAdapter gizmoAdapter;
+       protected OxmModelAndProcessorHelper oxmHelper;
+
+       /*
+        * We need to add another concept to the OxmModelLoader which is to generate
+        * a list of entity containers from the OXM JaxbContext
+        */
+
+       public GizmoEntitySummarizer()
+                       throws ElasticSearchOperationException, IOException, RestClientConstructionException {
+
+               OxmModelAndProcessorHelper.API_VERSION_OVERRIDE = 11;
+
+               this.gizmoExecutor = NodeUtils.createNamedExecutor("GIZMO-WORKER", 5, logger);
+
+               oxmHelper = OxmModelAndProcessorHelper.getInstance();
+               this.oxmModelLoader = oxmHelper.getModelLoader();
+
+               this.mapper = new ObjectMapper();
+
+               RestEndpointConfig gizmoConfig = new RestEndpointConfig();
+
+               gizmoConfig.setEndpointIpAddress("10.147.138.153");
+               gizmoConfig.setEndpointServerPort("9520");
+               gizmoConfig.setNumRequestRetries(5);
+               gizmoConfig.setRestAuthenticationMode(RestAuthenticationMode.SSL_CERT);
+               gizmoConfig.setConnectTimeoutInMs(60000);
+               gizmoConfig.setReadTimeoutInMs(30000);
+               gizmoConfig.setCertFileName("client-cert-onap.p12");
+               gizmoConfig.setCertPassword("OBF:1y0q1uvc1uum1uvg1pil1pjl1uuq1uvk1uuu1y10");
+               gizmoConfig.setTruststoreFileName("synchronizer.jks");
+               gizmoConfig.setValidateServerCertChain(false);
+               gizmoConfig.setValidateServerHostname(false);
+
+               gizmoAdapter = new GizmoAdapter(oxmModelLoader, gizmoConfig);
+
+               gizmoAdapter.setInventoryBasePath("/services/inventory/v12/");
+               gizmoAdapter.setRelationshipsBasePath("/services/inventory/relationships/v12/");
+
+       }
+
+       private Map<String, Integer> getNumEntitiesPerType() {
+
+               Collection<String> containerTypes = oxmHelper.getOxmEntityContainerLookup().getEntityContainers();
+               Collection<String> links = new ArrayList<String>();
+               Map<String, Integer> entityTypeCounts = new TreeMap<String, Integer>();
+
+               final CountDownLatch latch = new CountDownLatch(containerTypes.size());
+
+               for (String entityType : containerTypes) {
+
+                       supplyAsync(new Supplier<Void>() {
+
+                               @Override
+                               public Void get() {
+
+                                       OperationResult typeLinksResult = null;
+                                       try {
+                                               typeLinksResult = gizmoAdapter.queryGizmoWithRetries(
+                                                               gizmoAdapter.getFullInventoryUrl(entityType), "application/json", 1);
+
+                                               if (typeLinksResult != null) {
+
+                                                       if (typeLinksResult.wasSuccessful() && typeLinksResult.getResult() != null) {
+
+                                                               JsonNode rootNode = mapper.readValue(typeLinksResult.getResult(), JsonNode.class);
+
+                                                               if (rootNode.isArray()) {
+                                                                       ArrayNode arrayNode = (ArrayNode) rootNode;
+                                                                       entityTypeCounts.put(entityType, new Integer(arrayNode.size()));
+                                                               } else {
+                                                                       entityTypeCounts.put(entityType, new Integer(-1));
+                                                               }
+
+                                                       } else {
+                                                               // -1
+                                                               entityTypeCounts.put(entityType, new Integer(-1));
+                                                       }
+
+                                               }
+
+                                       } catch (Exception exc) {
+                                               entityTypeCounts.put(entityType, new Integer(-1));
+                                       }
+
+                                       return null;
+                               }
+
+                       }, gizmoExecutor).whenComplete((result, error) -> {
+
+                               latch.countDown();
+
+                               if (error != null) {
+                                       logger.error(AaiUiMsgs.ERROR_GENERIC,
+                                                       "An error occurred getting data from AAI. Error = " + error.getMessage());
+                               }
+
+                       });
+
+               }
+
+               // System.out.println("self links size = " + selflinks.size());
+
+               try {
+                       latch.await();
+               } catch (InterruptedException e) {
+
+               }
+
+               return entityTypeCounts;
+       }
+
+       private Map<String, Integer> getNumRelationshipsPerType() {
+
+               Map<String, Integer> entityTypeCounts = new TreeMap<String, Integer>();
+
+               final CountDownLatch latch = new CountDownLatch(1);
+
+               supplyAsync(new Supplier<Void>() {
+
+                       @Override
+                       public Void get() {
+
+                               OperationResult typeLinksResult = null;
+                               try {
+                                       typeLinksResult = gizmoAdapter.queryGizmoWithRetries(gizmoAdapter.getFullRelationshipUrl("has"),
+                                                       "application/json", 1);
+
+                                       if (typeLinksResult != null) {
+
+                                               if (typeLinksResult.wasSuccessful() && typeLinksResult.getResult() != null) {
+
+                                                       JsonNode rootNode = mapper.readValue(typeLinksResult.getResult(), JsonNode.class);
+
+                                                       if (rootNode.isArray()) {
+                                                               ArrayNode arrayNode = (ArrayNode) rootNode;
+                                                               entityTypeCounts.put("has", new Integer(arrayNode.size()));
+                                                       } else {
+                                                               entityTypeCounts.put("has", new Integer(-1));
+                                                       }
+
+                                               } else {
+                                                       // -1
+                                                       entityTypeCounts.put("has", new Integer(-1));
+                                               }
+
+                                       } else {
+                                               entityTypeCounts.put("has", new Integer(-1));
+                                       }
+
+                               } catch (Exception exc) {
+                                       entityTypeCounts.put("has", new Integer(-1));
+                               }
+
+                               return null;
+                       }
+
+               }, gizmoExecutor).whenComplete((result, error) -> {
+
+                       latch.countDown();
+
+                       if (error != null) {
+                               logger.error(AaiUiMsgs.ERROR_GENERIC,
+                                               "An error occurred getting data from AAI. Error = " + error.getMessage());
+                       }
+
+               });
+
+               // System.out.println("self links size = " + selflinks.size());
+
+               try {
+                       latch.await();
+               } catch (InterruptedException e) {
+
+               }
+
+               return entityTypeCounts;
+       }
+
+       public void shutdown() {
+               this.gizmoExecutor.shutdown();
+       }
+
+       public static void main(String[] args)
+                       throws ElasticSearchOperationException, IOException, RestClientConstructionException {
+
+               System.setProperty("CONFIG_HOME", "X:\\2018_dev\\OSEAAI\\gizmo_integration\\onap_sparky-be\\appconfig-local\\");
+               GizmoEntitySummarizer gizmoSummarizer = new GizmoEntitySummarizer();
+
+               Map<String, Integer> entityCounts = gizmoSummarizer.getNumEntitiesPerType();
+               Map<String, Integer> relationshipCounts = gizmoSummarizer.getNumRelationshipsPerType();
+               gizmoSummarizer.shutdown();
+
+               System.out.println("Gizmo Entities:");
+
+               for (Entry<String, Integer> entry : entityCounts.entrySet()) {
+                       String key = entry.getKey();
+                       Integer value = entry.getValue();
+
+                       System.out.printf("\t%s : %d\n", key, value);
+               }
+
+               System.out.println("\nGizmo Relationships:");
+
+               for (Entry<String, Integer> entry : relationshipCounts.entrySet()) {
+                       String key = entry.getKey();
+                       Integer value = entry.getValue();
+
+                       System.out.printf("\t%s : %d\n", key, value);
+               }
+
+       }
+
+}
\ No newline at end of file
index bf5df76..48c8290 100644 (file)
@@ -5,6 +5,7 @@ import java.util.Set;
 
 import org.onap.aai.sparky.config.oxm.CrossEntityReferenceLookup;
 import org.onap.aai.sparky.config.oxm.GeoEntityLookup;
+import org.onap.aai.sparky.config.oxm.OxmEntityContainerLookup;
 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
 import org.onap.aai.sparky.config.oxm.OxmModelLoader;
 import org.onap.aai.sparky.config.oxm.OxmModelProcessor;
@@ -13,109 +14,123 @@ import org.onap.aai.sparky.config.oxm.SuggestionEntityLookup;
 import org.onap.aai.sparky.search.filters.config.FiltersConfig;
 
 public class OxmModelAndProcessorHelper {
-  
-  private static final int API_VERSION_OVERRIDE = -1;
-
-  private OxmModelLoader modelLoader;
-  private Set<OxmModelProcessor> processors;
-  
-  private CrossEntityReferenceLookup crossEntityReferenceLookup;
-  private GeoEntityLookup geoEntityLookup;
-  private OxmEntityLookup oxmEntityLookup;
-  private SearchableEntityLookup searchableEntityLookup;
-  private SuggestionEntityLookup suggestionEntityLookup;
-  private FiltersConfig filtersConfig;
-
-  private static OxmModelAndProcessorHelper instance = null;
-  private OxmModelAndProcessorHelper() {
-
-    this.filtersConfig = new FiltersConfig(SparkyTestConstants.FILTERS_JSON_FILE, SparkyTestConstants.VIEWS_JSON_FILE);
-    
-    this.crossEntityReferenceLookup = new CrossEntityReferenceLookup();
-    this.geoEntityLookup = new GeoEntityLookup();
-    this.oxmEntityLookup = new OxmEntityLookup();
-    this.searchableEntityLookup = new SearchableEntityLookup();
-    this.suggestionEntityLookup = new SuggestionEntityLookup(filtersConfig);
-    
-    this.processors = new HashSet<OxmModelProcessor>();
-    processors.add(crossEntityReferenceLookup);
-    processors.add(geoEntityLookup);
-    processors.add(oxmEntityLookup);
-    processors.add(searchableEntityLookup);
-    processors.add(suggestionEntityLookup);
-    
-    this.modelLoader = new OxmModelLoader(API_VERSION_OVERRIDE, processors);
-    modelLoader.loadLatestOxmModel();
-  }
-  
-  public static OxmModelAndProcessorHelper getInstance() {
-    if (instance == null) {
-      instance = new OxmModelAndProcessorHelper();
-    }
-    return instance;
-  }
-
-  public OxmModelLoader getModelLoader() {
-    return modelLoader;
-  }
-
-  public void setModelLoader(OxmModelLoader modelLoader) {
-    this.modelLoader = modelLoader;
-  }
-
-  public Set<OxmModelProcessor> getProcessors() {
-    return processors;
-  }
-
-  public void setProcessors(Set<OxmModelProcessor> processors) {
-    this.processors = processors;
-  }
-
-  public CrossEntityReferenceLookup getCrossEntityReferenceLookup() {
-    return crossEntityReferenceLookup;
-  }
-
-  public void setCrossEntityReferenceLookup(CrossEntityReferenceLookup crossEntityReferenceLookup) {
-    this.crossEntityReferenceLookup = crossEntityReferenceLookup;
-  }
-
-  public GeoEntityLookup getGeoEntityLookup() {
-    return geoEntityLookup;
-  }
-
-  public void setGeoEntityLookup(GeoEntityLookup geoEntityLookup) {
-    this.geoEntityLookup = geoEntityLookup;
-  }
-
-  public OxmEntityLookup getOxmEntityLookup() {
-    return oxmEntityLookup;
-  }
-
-  public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) {
-    this.oxmEntityLookup = oxmEntityLookup;
-  }
-
-  public SearchableEntityLookup getSearchableEntityLookup() {
-    return searchableEntityLookup;
-  }
-
-  public void setSearchableEntityLookup(SearchableEntityLookup searchableEntityLookup) {
-    this.searchableEntityLookup = searchableEntityLookup;
-  }
-
-  public SuggestionEntityLookup getSuggestionEntityLookup() {
-    return suggestionEntityLookup;
-  }
-
-  public void setSuggestionEntityLookup(SuggestionEntityLookup suggestionEntityLookup) {
-    this.suggestionEntityLookup = suggestionEntityLookup;
-  }
-
-  public FiltersConfig getFiltersConfig() {
-    return filtersConfig;
-  }
-
-  public void setFiltersConfig(FiltersConfig filtersConfig) {
-    this.filtersConfig = filtersConfig;
-  }
+
+       public static int API_VERSION_OVERRIDE = -1;
+
+       private OxmModelLoader modelLoader;
+       private Set<OxmModelProcessor> processors;
+
+       private CrossEntityReferenceLookup crossEntityReferenceLookup;
+       private GeoEntityLookup geoEntityLookup;
+       private OxmEntityLookup oxmEntityLookup;
+       private SearchableEntityLookup searchableEntityLookup;
+       private SuggestionEntityLookup suggestionEntityLookup;
+       private OxmEntityContainerLookup oxmEntityContainerLookup;
+       private FiltersConfig filtersConfig;
+
+       private static OxmModelAndProcessorHelper instance = null;
+
+       private OxmModelAndProcessorHelper() {
+
+               this.filtersConfig = new FiltersConfig(SparkyTestConstants.FILTERS_JSON_FILE,
+                               SparkyTestConstants.VIEWS_JSON_FILE);
+
+               this.crossEntityReferenceLookup = new CrossEntityReferenceLookup();
+               this.geoEntityLookup = new GeoEntityLookup();
+               this.oxmEntityLookup = new OxmEntityLookup();
+               this.searchableEntityLookup = new SearchableEntityLookup();
+               this.suggestionEntityLookup = new SuggestionEntityLookup(filtersConfig);
+               this.oxmEntityContainerLookup = new OxmEntityContainerLookup();
+
+               this.processors = new HashSet<OxmModelProcessor>();
+               processors.add(crossEntityReferenceLookup);
+               processors.add(geoEntityLookup);
+               processors.add(oxmEntityLookup);
+               processors.add(searchableEntityLookup);
+               processors.add(suggestionEntityLookup);
+               processors.add(oxmEntityContainerLookup);
+
+               this.modelLoader = new OxmModelLoader(API_VERSION_OVERRIDE, processors);
+               modelLoader.loadLatestOxmModel();
+       }
+
+       public static OxmModelAndProcessorHelper getInstance() {
+               if (instance == null) {
+                       instance = new OxmModelAndProcessorHelper();
+               }
+               return instance;
+       }
+
+       public OxmModelLoader getModelLoader() {
+               return modelLoader;
+       }
+
+       public void setModelLoader(OxmModelLoader modelLoader) {
+               this.modelLoader = modelLoader;
+       }
+
+       public Set<OxmModelProcessor> getProcessors() {
+               return processors;
+       }
+
+       public void setProcessors(Set<OxmModelProcessor> processors) {
+               this.processors = processors;
+       }
+
+       public CrossEntityReferenceLookup getCrossEntityReferenceLookup() {
+               return crossEntityReferenceLookup;
+       }
+
+       public void setCrossEntityReferenceLookup(CrossEntityReferenceLookup crossEntityReferenceLookup) {
+               this.crossEntityReferenceLookup = crossEntityReferenceLookup;
+       }
+
+       public GeoEntityLookup getGeoEntityLookup() {
+               return geoEntityLookup;
+       }
+
+       public void setGeoEntityLookup(GeoEntityLookup geoEntityLookup) {
+               this.geoEntityLookup = geoEntityLookup;
+       }
+
+       public OxmEntityLookup getOxmEntityLookup() {
+               return oxmEntityLookup;
+       }
+
+       public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) {
+               this.oxmEntityLookup = oxmEntityLookup;
+       }
+
+       public SearchableEntityLookup getSearchableEntityLookup() {
+               return searchableEntityLookup;
+       }
+
+       public void setSearchableEntityLookup(SearchableEntityLookup searchableEntityLookup) {
+               this.searchableEntityLookup = searchableEntityLookup;
+       }
+
+       public SuggestionEntityLookup getSuggestionEntityLookup() {
+               return suggestionEntityLookup;
+       }
+
+       public void setSuggestionEntityLookup(SuggestionEntityLookup suggestionEntityLookup) {
+               this.suggestionEntityLookup = suggestionEntityLookup;
+       }
+
+       public FiltersConfig getFiltersConfig() {
+               return filtersConfig;
+       }
+
+       public void setFiltersConfig(FiltersConfig filtersConfig) {
+               this.filtersConfig = filtersConfig;
+       }
+
+       public OxmEntityContainerLookup getOxmEntityContainerLookup() {
+               return oxmEntityContainerLookup;
+       }
+
+       public void setOxmEntityContainerLookup(OxmEntityContainerLookup oxmEntityContainerLookup) {
+               this.oxmEntityContainerLookup = oxmEntityContainerLookup;
+       }
+
 }
index bc1a80d..e51c629 100644 (file)
@@ -1,6 +1,6 @@
 package org.onap.aai.sparky.viewandinspect;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -9,6 +9,7 @@ import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
 import org.onap.aai.sparky.config.oxm.OxmModelLoader;
 import org.onap.aai.sparky.dal.ActiveInventoryAdapter;
 import org.onap.aai.sparky.dal.ElasticSearchAdapter;
+import org.onap.aai.sparky.dal.GizmoAdapter;
 import org.onap.aai.sparky.subscription.config.SubscriptionConfig;
 import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig;
 import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig;
@@ -26,13 +27,16 @@ public class BaseVisualizationServiceTest {
   private ElasticSearchEndpointConfig endpointEConfig;
   private ElasticSearchSchemaConfig schemaEConfig;
   private OxmEntityLookup oxmEntityLookup;
+  private GizmoAdapter mockGizmoAdapter;
   
   private BaseVisualizationService baseVisService;
   
   @Before
   public void init() throws Exception {
+    this.mockAaiAdapter = Mockito.mock(ActiveInventoryAdapter.class);
     this.mockAaiAdapter = Mockito.mock(ActiveInventoryAdapter.class);
     this.mockEsAdapter = Mockito.mock(ElasticSearchAdapter.class);
+    this.mockGizmoAdapter = Mockito.mock(GizmoAdapter.class);
     this.visualizationConfigs = new VisualizationConfigs();
     this.subConfig = new SubscriptionConfig();
     this.endpointEConfig = new ElasticSearchEndpointConfig();
@@ -41,8 +45,9 @@ public class BaseVisualizationServiceTest {
     
     OxmModelLoader modelLoader = OxmModelAndProcessorHelper.getInstance().getModelLoader();
     
-    this.baseVisService = new BaseVisualizationService(modelLoader, visualizationConfigs, mockAaiAdapter,
-        mockEsAdapter, endpointEConfig, schemaEConfig, 1, oxmEntityLookup, subConfig);
+    this.baseVisService = new BaseVisualizationService(modelLoader, visualizationConfigs,
+        mockAaiAdapter, mockGizmoAdapter, mockEsAdapter, endpointEConfig, schemaEConfig, 1,
+        oxmEntityLookup, subConfig);
   }
   
   @Test
diff --git a/src/test/java/org/onap/aai/sparky/viewandinspect/sync/ViewInspectGizmoEntitySynchronizer.java b/src/test/java/org/onap/aai/sparky/viewandinspect/sync/ViewInspectGizmoEntitySynchronizer.java
new file mode 100644 (file)
index 0000000..6d63a8a
--- /dev/null
@@ -0,0 +1,792 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.viewandinspect.sync;
+
+import static java.util.concurrent.CompletableFuture.supplyAsync;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
+
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.aai.cl.mdc.MdcContext;
+import org.onap.aai.restclient.client.OperationResult;
+import org.onap.aai.sparky.config.oxm.OxmEntityContainerLookup;
+import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
+import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
+import org.onap.aai.sparky.config.oxm.SearchableEntityLookup;
+import org.onap.aai.sparky.config.oxm.SearchableOxmEntityDescriptor;
+import org.onap.aai.sparky.dal.GizmoAdapter;
+import org.onap.aai.sparky.dal.NetworkTransaction;
+import org.onap.aai.sparky.dal.rest.HttpMethod;
+import org.onap.aai.sparky.logging.AaiUiMsgs;
+import org.onap.aai.sparky.sync.AbstractEntitySynchronizer;
+import org.onap.aai.sparky.sync.IndexSynchronizer;
+import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig;
+import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig;
+import org.onap.aai.sparky.sync.entity.MergableEntity;
+import org.onap.aai.sparky.sync.entity.SearchableEntity;
+import org.onap.aai.sparky.sync.entity.SelfLinkDescriptor;
+import org.onap.aai.sparky.sync.enumeration.OperationState;
+import org.onap.aai.sparky.sync.enumeration.SynchronizerState;
+import org.onap.aai.sparky.sync.task.PerformElasticSearchPut;
+import org.onap.aai.sparky.sync.task.PerformElasticSearchRetrieval;
+import org.onap.aai.sparky.sync.task.PerformElasticSearchUpdate;
+import org.onap.aai.sparky.sync.task.PerformGizmoRetrieval;
+import org.onap.aai.sparky.util.NodeUtils;
+import org.slf4j.MDC;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+/**
+ * The Class SearchableEntitySynchronizer.
+ */
+public class ViewInspectGizmoEntitySynchronizer extends AbstractEntitySynchronizer
+    implements IndexSynchronizer {
+
+  /**
+   * The Class RetrySearchableEntitySyncContainer.
+   */
+  private class RetrySearchableEntitySyncContainer {
+    NetworkTransaction txn;
+    SearchableEntity se;
+
+    /**
+     * Instantiates a new retry searchable entity sync container.
+     *
+     * @param txn the txn
+     * @param se the se
+     */
+    public RetrySearchableEntitySyncContainer(NetworkTransaction txn, SearchableEntity se) {
+      this.txn = txn;
+      this.se = se;
+    }
+
+    public NetworkTransaction getNetworkTransaction() {
+      return txn;
+    }
+
+    public SearchableEntity getSearchableEntity() {
+      return se;
+    }
+  }
+
+  private static final Logger LOG =
+      LoggerFactory.getInstance().getLogger(ViewInspectGizmoEntitySynchronizer.class);
+
+  private boolean allWorkEnumerated;
+  private Deque<SelfLinkDescriptor> selflinks;
+  private Deque<RetrySearchableEntitySyncContainer> retryQueue;
+  private Map<String, Integer> retryLimitTracker;
+  protected ExecutorService esPutExecutor;
+  private OxmEntityLookup oxmEntityLookup;
+  private SearchableEntityLookup searchableEntityLookup;
+  private GizmoAdapter gizmoAdapter;
+  private OxmEntityContainerLookup entityContainerLookup;
+
+  /**
+   * Instantiates a new searchable entity synchronizer.
+   *
+   * @param indexName the index name
+   * @throws Exception the exception
+   */
+  public ViewInspectGizmoEntitySynchronizer(ElasticSearchSchemaConfig schemaConfig,
+      int internalSyncWorkers, int gizmoWorkers, int esWorkers, NetworkStatisticsConfig aaiStatConfig,
+      NetworkStatisticsConfig esStatConfig, OxmEntityLookup oxmEntityLookup,
+      SearchableEntityLookup searchableEntityLookup, OxmEntityContainerLookup entityContainerLookup) throws Exception {
+    super(LOG, "SES", internalSyncWorkers, gizmoWorkers, esWorkers, schemaConfig.getIndexName(),
+        aaiStatConfig, esStatConfig);
+    
+    this.oxmEntityLookup = oxmEntityLookup;
+    this.searchableEntityLookup = searchableEntityLookup;
+    this.entityContainerLookup = entityContainerLookup;
+    this.allWorkEnumerated = false;
+    this.selflinks = new ConcurrentLinkedDeque<SelfLinkDescriptor>();
+    this.retryQueue = new ConcurrentLinkedDeque<RetrySearchableEntitySyncContainer>();
+    this.retryLimitTracker = new ConcurrentHashMap<String, Integer>();
+    this.synchronizerName = "Searchable Entity Synchronizer";
+    this.esPutExecutor = NodeUtils.createNamedExecutor("SES-ES-PUT", 5, LOG);
+    this.aaiEntityStats.intializeEntityCounters(
+        searchableEntityLookup.getSearchableEntityDescriptors().keySet());
+    this.esEntityStats.intializeEntityCounters(
+        searchableEntityLookup.getSearchableEntityDescriptors().keySet());
+    this.syncDurationInMs = -1;
+  }
+  
+  
+
+  public GizmoAdapter getGizmoAdapter() {
+       return gizmoAdapter;
+}
+
+
+
+public void setGizmoAdapter(GizmoAdapter gizmoAdapter) {
+       this.gizmoAdapter = gizmoAdapter;
+}
+
+
+
+/**
+   * Collect all the work.
+   *
+   * @return the operation state
+   */
+  private OperationState collectAllTheWork() {
+    final Map<String, String> contextMap = MDC.getCopyOfContextMap();
+    
+    Collection<String> searchableEntityGroups = entityContainerLookup.getSearchableEntityGroups();
+    
+    if (searchableEntityGroups.isEmpty()) {
+      LOG.error(AaiUiMsgs.ERROR_LOADING_OXM_SEARCHABLE_ENTITIES);
+      return OperationState.ERROR;
+    }
+
+
+    try {
+
+      /*
+       * launch a parallel async thread to process the documents for each entity-type (to max the
+       * of the configured executor anyway)
+       */
+
+       /*searchableEntityGroups = new ArrayList<String>();
+       searchableEntityGroups.add("pservers");*/
+       
+      aaiWorkOnHand.set(searchableEntityGroups.size());
+
+      for (String searchableEntityGroup : searchableEntityGroups) {
+
+        supplyAsync(new Supplier<Void>() {
+
+          @Override
+          public Void get() {
+            MDC.setContextMap(contextMap);
+            OperationResult typeLinksResult = null;
+            try {
+              typeLinksResult = gizmoAdapter.getSelfLinksByEntityType(searchableEntityGroup);
+              aaiWorkOnHand.decrementAndGet();
+              processEntityTypeSelfLinks(typeLinksResult);
+            } catch (Exception exc) {
+
+               exc.printStackTrace();
+            }
+
+            return null;
+          }
+
+        }, aaiExecutor).whenComplete((result, error) -> {
+
+          if (error != null) {
+            LOG.error(AaiUiMsgs.ERROR_GENERIC,
+                "An error occurred getting data from AAI. Error = " + error.getMessage());
+          }
+        });
+
+      }
+
+      while (aaiWorkOnHand.get() != 0) {
+
+        if (LOG.isDebugEnabled()) {
+          LOG.debug(AaiUiMsgs.WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED);
+        }
+
+        Thread.sleep(1000);
+      }
+
+      aaiWorkOnHand.set(selflinks.size());
+      allWorkEnumerated = true;
+      syncEntityTypes();
+
+      while (!isSyncDone()) {
+        performRetrySync();
+        Thread.sleep(1000);
+      }
+
+      /*
+       * Make sure we don't hang on to retries that failed which could cause issues during future
+       * syncs
+       */
+      retryLimitTracker.clear();
+
+    } catch (Exception exc) {
+      // TODO -> LOG, waht should be logged here?
+    }
+
+    return OperationState.OK;
+  }
+
+  /* (non-Javadoc)
+   * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync()
+   */
+  @Override
+  public OperationState doSync() {
+    this.syncDurationInMs = -1;
+    String txnID = NodeUtils.getRandomTxnId();
+    MdcContext.initialize(txnID, "SearchableEntitySynchronizer", "", "Sync", "");
+    
+    resetCounters();
+    this.allWorkEnumerated = false;
+    syncStartedTimeStampInMs = System.currentTimeMillis();
+    collectAllTheWork();
+
+    return OperationState.OK;
+  }
+
+  /**
+   * Process entity type self links.
+   *
+   * @param operationResult the operation result
+   */
+       private void processEntityTypeSelfLinks(OperationResult operationResult) {
+
+               JsonNode rootNode = null;
+
+               final String jsonResult = operationResult.getResult();
+
+               if (jsonResult != null && jsonResult.length() > 0 && operationResult.wasSuccessful()) {
+                       
+                       try {
+                               rootNode = mapper.readTree(jsonResult);
+                       } catch (IOException exc) {
+                               String message = "Could not deserialize JSON (representing operation result) as node tree. "
+                                               + "Operation result = " + jsonResult + ". " + exc.getLocalizedMessage();
+                               LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, message);
+                       }
+
+                       ArrayNode resultDataArrayNode = null;
+
+                       if (rootNode.isArray()) {
+                               resultDataArrayNode = (ArrayNode) rootNode;
+                               
+                               Iterator<JsonNode> elementIterator = resultDataArrayNode.elements();
+                               JsonNode element = null;
+
+                               while (elementIterator.hasNext()) {
+                                       element = elementIterator.next();
+
+                                       final String id = NodeUtils.getNodeFieldAsText(element, "id");
+                                       final String type = NodeUtils.getNodeFieldAsText(element, "type");
+                                       final String url = NodeUtils.getNodeFieldAsText(element, "url");
+
+                                       String resourceLink;
+                                       try {
+                                               resourceLink = gizmoAdapter.getFullInventoryUrl(type + "/" + id);
+                                               selflinks.add(new SelfLinkDescriptor(NodeUtils.extractRawGizmoPathWithoutVersion(resourceLink), null, type));
+                                       } catch (Exception e) {
+                                         LOG.error(AaiUiMsgs.ERROR_GENERIC, "ERROR:  Failed to determine resource link caused by " + e.getMessage());
+                                       }
+
+                               }
+                       }
+               }
+
+       }
+
+  /**
+   * Sync entity types.
+   */
+  private void syncEntityTypes() {
+
+    while (selflinks.peek() != null) {
+
+      SelfLinkDescriptor linkDescriptor = selflinks.poll();
+      aaiWorkOnHand.decrementAndGet();
+
+      OxmEntityDescriptor descriptor = null;
+
+      if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) {
+
+        descriptor = oxmEntityLookup.getEntityDescriptors().get(linkDescriptor.getEntityType());
+
+        if (descriptor == null) {
+          LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType());
+          continue;
+        }
+
+        NetworkTransaction txn = new NetworkTransaction();
+        txn.setDescriptor(descriptor);
+        txn.setLink(linkDescriptor.getSelfLink());
+        txn.setOperationType(HttpMethod.GET);
+        txn.setEntityType(linkDescriptor.getEntityType());
+
+        aaiWorkOnHand.incrementAndGet();
+
+        supplyAsync(new PerformGizmoRetrieval(txn, gizmoAdapter), aaiExecutor)
+            .whenComplete((result, error) -> {
+
+              aaiWorkOnHand.decrementAndGet();
+
+              if (error != null) {
+                LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_GENERIC, error.getLocalizedMessage());
+              } else {
+                if (result == null) {
+                  LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_FOR_SELF_LINK,
+                      linkDescriptor.getSelfLink());
+                } else {
+                  updateActiveInventoryCounters(result);
+                  fetchDocumentForUpsert(result);
+                }
+              }
+            });
+      }
+
+    }
+
+  }
+
+  /**
+   * Perform document upsert.
+   *
+   * @param esGetTxn the es get txn
+   * @param se the se
+   */
+  protected void performDocumentUpsert(NetworkTransaction esGetTxn, SearchableEntity se) {
+    /**
+     * <p>
+     * <ul>
+     * As part of the response processing we need to do the following:
+     * <li>1. Extract the version (if present), it will be the ETAG when we use the
+     * Search-Abstraction-Service
+     * <li>2. Spawn next task which is to do the PUT operation into elastic with or with the version
+     * tag
+     * <li>a) if version is null or RC=404, then standard put, no _update with version tag
+     * <li>b) if version != null, do PUT with _update?version= versionNumber in the URI to elastic
+     * </ul>
+     * </p>
+     */
+    String link = null;
+    try {
+      link = elasticSearchAdapter.buildElasticSearchGetDocUrl(getIndexName(), se.getId());
+    } catch (Exception exc) {
+      LOG.error(AaiUiMsgs.ES_LINK_UPSERT, exc.getLocalizedMessage());
+      return;
+    }
+
+    String versionNumber = null;
+    boolean wasEntryDiscovered = false;
+    if (esGetTxn.getOperationResult().getResultCode() == 404) {
+      LOG.info(AaiUiMsgs.ES_SIMPLE_PUT, se.getEntityPrimaryKeyValue());
+    } else if (esGetTxn.getOperationResult().getResultCode() == 200) {
+      wasEntryDiscovered = true;
+      try {
+        versionNumber = NodeUtils.extractFieldValueFromObject(
+            NodeUtils.convertJsonStrToJsonNode(esGetTxn.getOperationResult().getResult()),
+            "_version");
+      } catch (IOException exc) {
+        String message =
+            "Error extracting version number from response, aborting searchable entity sync of "
+                + se.getEntityPrimaryKeyValue() + ". Error - " + exc.getLocalizedMessage();
+        LOG.error(AaiUiMsgs.ERROR_EXTRACTING_FROM_RESPONSE, message);
+        return;
+      }
+    } else {
+      /*
+       * Not being a 200 does not mean a failure. eg 201 is returned for created. TODO -> Should we
+       * return.
+       */
+      LOG.error(AaiUiMsgs.ES_OPERATION_RETURN_CODE,
+          String.valueOf(esGetTxn.getOperationResult().getResultCode()));
+      return;
+    }
+
+    try {
+      String jsonPayload = null;
+      if (wasEntryDiscovered) {
+        try {
+          ArrayList<JsonNode> sourceObject = new ArrayList<JsonNode>();
+          NodeUtils.extractObjectsByKey(
+              NodeUtils.convertJsonStrToJsonNode(esGetTxn.getOperationResult().getResult()),
+              "_source", sourceObject);
+
+          if (!sourceObject.isEmpty()) {
+            String responseSource = NodeUtils.convertObjectToJson(sourceObject.get(0), false);
+            MergableEntity me = mapper.readValue(responseSource, MergableEntity.class);
+            ObjectReader updater = mapper.readerForUpdating(me);
+            MergableEntity merged = updater.readValue(NodeUtils.convertObjectToJson(se,false));
+            jsonPayload = mapper.writeValueAsString(merged);
+          }
+        } catch (IOException exc) {
+          String message =
+              "Error extracting source value from response, aborting searchable entity sync of "
+                  + se.getEntityPrimaryKeyValue() + ". Error - " + exc.getLocalizedMessage();
+          LOG.error(AaiUiMsgs.ERROR_EXTRACTING_FROM_RESPONSE, message);
+          return;
+        }
+      } else {
+          jsonPayload = se.getAsJson();
+      }
+
+      if (wasEntryDiscovered) {
+        if (versionNumber != null && jsonPayload != null) {
+
+          String requestPayload = elasticSearchAdapter.buildBulkImportOperationRequest(getIndexName(),
+              "default", se.getId(), versionNumber, jsonPayload);
+
+          NetworkTransaction transactionTracker = new NetworkTransaction();
+          transactionTracker.setEntityType(esGetTxn.getEntityType());
+          transactionTracker.setDescriptor(esGetTxn.getDescriptor());
+          transactionTracker.setOperationType(HttpMethod.PUT);
+
+          esWorkOnHand.incrementAndGet();
+          supplyAsync(new PerformElasticSearchUpdate(elasticSearchAdapter.getBulkUrl(),
+              requestPayload, elasticSearchAdapter, transactionTracker), esPutExecutor)
+                  .whenComplete((result, error) -> {
+
+                    esWorkOnHand.decrementAndGet();
+
+                    if (error != null) {
+                      String message = "Searchable entity sync UPDATE PUT error - "
+                          + error.getLocalizedMessage();
+                      LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message);
+                    } else {
+                      updateElasticSearchCounters(result);
+                      processStoreDocumentResult(result, esGetTxn, se);
+                    }
+                  });
+        }
+
+      } else {
+        
+        if (link != null && jsonPayload != null) {
+
+          NetworkTransaction updateElasticTxn = new NetworkTransaction();
+          updateElasticTxn.setLink(link);
+          updateElasticTxn.setEntityType(esGetTxn.getEntityType());
+          updateElasticTxn.setDescriptor(esGetTxn.getDescriptor());
+          updateElasticTxn.setOperationType(HttpMethod.PUT);
+
+          esWorkOnHand.incrementAndGet();
+          supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, elasticSearchAdapter),
+              esPutExecutor).whenComplete((result, error) -> {
+
+                esWorkOnHand.decrementAndGet();
+
+                if (error != null) {
+                  String message =
+                      "Searchable entity sync UPDATE PUT error - " + error.getLocalizedMessage();
+                  LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message);
+                } else {
+                  updateElasticSearchCounters(result);
+                  processStoreDocumentResult(result, esGetTxn, se);
+                }
+              });
+        }
+      }
+    } catch (Exception exc) {
+      String message = "Exception caught during searchable entity sync PUT operation. Message - "
+          + exc.getLocalizedMessage();
+      LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message);
+    }
+  }
+
+  /**
+   * Populate searchable entity document.
+   *
+   * @param doc the doc
+   * @param result the result
+   * @param resultDescriptor the result descriptor
+   * @throws JsonProcessingException the json processing exception
+   * @throws IOException Signals that an I/O exception has occurred.
+   */
+  protected void populateSearchableEntityDocument(SearchableEntity doc, String result,
+      OxmEntityDescriptor resultDescriptor) throws JsonProcessingException, IOException {
+
+    doc.setEntityType(resultDescriptor.getEntityName());
+
+    JsonNode entityNode = mapper.readTree(result);
+    
+    String id = NodeUtils.getNodeFieldAsText(entityNode, "id");
+    String type = NodeUtils.getNodeFieldAsText(entityNode, "type");
+    String url = NodeUtils.getNodeFieldAsText(entityNode, "url");
+    
+    JsonNode properties = entityNode.get("properties");
+    
+    Iterator<String> fieldNames = properties.fieldNames();
+    
+    
+
+    List<String> primaryKeyValues = new ArrayList<String>();
+    String pkeyValue = null;
+
+    SearchableOxmEntityDescriptor searchableDescriptor = searchableEntityLookup.getSearchableEntityDescriptors().get(resultDescriptor.getEntityName());
+    
+    for (String keyName : searchableDescriptor.getPrimaryKeyAttributeNames()) {
+      pkeyValue = NodeUtils.getNodeFieldAsText(properties, keyName);
+      if (pkeyValue != null) {
+        primaryKeyValues.add(pkeyValue);
+      } else {
+        String message = "populateSearchableEntityDocument(), pKeyValue is null for entityType = "
+            + resultDescriptor.getEntityName();
+        LOG.warn(AaiUiMsgs.WARN_GENERIC, message);
+      }
+    }
+
+    final String primaryCompositeKeyValue = NodeUtils.concatArray(primaryKeyValues, "/");
+    doc.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
+
+    final List<String> searchTagFields = searchableDescriptor.getSearchableAttributes();
+
+    /*
+     * Based on configuration, use the configured field names for this entity-Type to build a
+     * multi-value collection of search tags for elastic search entity search criteria.
+     */
+    for (String searchTagField : searchTagFields) {
+      String searchTagValue = NodeUtils.getNodeFieldAsText(properties, searchTagField);
+      if (searchTagValue != null && !searchTagValue.isEmpty()) {
+        doc.addSearchTagWithKey(searchTagValue, searchTagField);
+      }
+    }
+  }
+
+  /**
+   * Fetch document for upsert.
+   *
+   * @param txn the txn
+   */
+  private void fetchDocumentForUpsert(NetworkTransaction txn) {
+    if (!txn.getOperationResult().wasSuccessful()) {
+      String message = "Self link failure. Result - " + txn.getOperationResult().getResult();
+      LOG.error(AaiUiMsgs.ERROR_GENERIC, message);
+      return;
+    }
+
+    SearchableOxmEntityDescriptor searchableDescriptor = searchableEntityLookup
+        .getSearchableEntityDescriptors().get(txn.getDescriptor().getEntityName());
+    
+    try {
+      if (searchableDescriptor.hasSearchableAttributes()) {
+
+        final String jsonResult = txn.getOperationResult().getResult();
+        if (jsonResult != null && jsonResult.length() > 0) {
+
+                SearchableEntity se = new SearchableEntity();
+             se.setLink( txn.getLink() );
+             populateSearchableEntityDocument(se, jsonResult, searchableDescriptor);
+             se.deriveFields();        
+               
+
+          String link = null;
+          try {
+            link = elasticSearchAdapter.buildElasticSearchGetDocUrl(getIndexName(), se.getId());
+          } catch (Exception exc) {
+            LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_QUERY, exc.getLocalizedMessage());
+          }
+
+          if (link != null) {
+            NetworkTransaction n2 = new NetworkTransaction();
+            n2.setLink(link);
+            n2.setEntityType(txn.getEntityType());
+            n2.setDescriptor(txn.getDescriptor());
+            n2.setOperationType(HttpMethod.GET);
+
+            esWorkOnHand.incrementAndGet();
+
+            supplyAsync(new PerformElasticSearchRetrieval(n2, elasticSearchAdapter), esExecutor)
+                .whenComplete((result, error) -> {
+
+                  esWorkOnHand.decrementAndGet();
+
+                  if (error != null) {
+                    LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED, error.getLocalizedMessage());
+                  } else {
+                    updateElasticSearchCounters(result);
+                    performDocumentUpsert(result, se);
+                  }
+                });
+          }
+        }
+
+      } 
+    } catch (JsonProcessingException exc) {
+      LOG.error(AaiUiMsgs.ERROR_GENERIC, "Processing error while fetching document for elasticsearch update.  Error: " + exc.getMessage() );
+    } catch (IOException exc) {
+      LOG.error(AaiUiMsgs.ERROR_GENERIC, "Processing error while fetching document for elasticsearch update.  Error: " + exc.getMessage() );
+    }
+  }
+
+  /**
+   * Process store document result.
+   *
+   * @param esPutResult the es put result
+   * @param esGetResult the es get result
+   * @param se the se
+   */
+  private void processStoreDocumentResult(NetworkTransaction esPutResult,
+      NetworkTransaction esGetResult, SearchableEntity se) {
+
+    OperationResult or = esPutResult.getOperationResult();
+
+    if (!or.wasSuccessful()) {
+      if (or.getResultCode() == VERSION_CONFLICT_EXCEPTION_CODE) {
+
+        if (shouldAllowRetry(se.getId())) {
+          esWorkOnHand.incrementAndGet();
+
+          RetrySearchableEntitySyncContainer rsc =
+              new RetrySearchableEntitySyncContainer(esGetResult, se);
+          retryQueue.push(rsc);
+
+          String message = "Store document failed during searchable entity synchronization"
+              + " due to version conflict. Entity will be re-synced.";
+          LOG.warn(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message);
+        }
+      } else {
+        String message =
+            "Store document failed during searchable entity synchronization with result code "
+                + or.getResultCode() + " and result message " + or.getResult();
+        LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message);
+      }
+    }
+  }
+
+  /**
+   * Perform retry sync.
+   */
+  private void performRetrySync() {
+    while (retryQueue.peek() != null) {
+
+      RetrySearchableEntitySyncContainer rsc = retryQueue.poll();
+      if (rsc != null) {
+
+        SearchableEntity se = rsc.getSearchableEntity();
+        NetworkTransaction txn = rsc.getNetworkTransaction();
+
+        String link = null;
+        try {
+          /*
+           * In this retry flow the se object has already derived its fields
+           */
+          link = elasticSearchAdapter.buildElasticSearchGetDocUrl(getIndexName(), se.getId());
+        } catch (Exception exc) {
+          LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_URI, exc.getLocalizedMessage());
+        }
+
+        if (link != null) {
+          NetworkTransaction retryTransaction = new NetworkTransaction();
+          retryTransaction.setLink(link);
+          retryTransaction.setEntityType(txn.getEntityType());
+          retryTransaction.setDescriptor(txn.getDescriptor());
+          retryTransaction.setOperationType(HttpMethod.GET);
+
+          /*
+           * IMPORTANT - DO NOT incrementAndGet the esWorkOnHand as this is a retry flow! We already
+           * called incrementAndGet when queuing the failed PUT!
+           */
+
+          supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, elasticSearchAdapter),
+              esExecutor).whenComplete((result, error) -> {
+
+                esWorkOnHand.decrementAndGet();
+
+                if (error != null) {
+                  LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED_RESYNC, error.getLocalizedMessage());
+                } else {
+                  updateElasticSearchCounters(result);
+                  performDocumentUpsert(result, se);
+                }
+              });
+        }
+
+      }
+    }
+  }
+
+  /**
+   * Should allow retry.
+   *
+   * @param id the id
+   * @return true, if successful
+   */
+  private boolean shouldAllowRetry(String id) {
+    boolean isRetryAllowed = true;
+    if (retryLimitTracker.get(id) != null) {
+      Integer currentCount = retryLimitTracker.get(id);
+      if (currentCount.intValue() >= RETRY_COUNT_PER_ENTITY_LIMIT.intValue()) {
+        isRetryAllowed = false;
+        String message = "Searchable entity re-sync limit reached for " + id
+            + ", re-sync will no longer be attempted for this entity";
+        LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message);
+      } else {
+        Integer newCount = new Integer(currentCount.intValue() + 1);
+        retryLimitTracker.put(id, newCount);
+      }
+    } else {
+      Integer firstRetryCount = new Integer(1);
+      retryLimitTracker.put(id, firstRetryCount);
+    }
+
+    return isRetryAllowed;
+  }
+
+  @Override
+  public SynchronizerState getState() {
+    if (!isSyncDone()) {
+      return SynchronizerState.PERFORMING_SYNCHRONIZATION;
+    }
+
+    return SynchronizerState.IDLE;
+
+  }
+
+  /* (non-Javadoc)
+   * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean)
+   */
+  @Override
+  public String getStatReport(boolean showFinalReport) {
+    syncDurationInMs = System.currentTimeMillis() - syncStartedTimeStampInMs;
+    return this.getStatReport(syncDurationInMs, showFinalReport);
+  }
+
+  /* (non-Javadoc)
+   * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown()
+   */
+  @Override
+  public void shutdown() {
+    this.shutdownExecutors();
+  }
+
+  @Override
+  protected boolean isSyncDone() {
+    int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get();
+
+    if (totalWorkOnHand > 0 || !allWorkEnumerated) {
+      return false;
+    }
+
+    return true;
+  }
+
+}
diff --git a/src/test/java/org/onap/aai/sparky/viewandinspect/sync/ViewInspectGizmoSyncController.java b/src/test/java/org/onap/aai/sparky/viewandinspect/sync/ViewInspectGizmoSyncController.java
new file mode 100644 (file)
index 0000000..1fdadfc
--- /dev/null
@@ -0,0 +1,106 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.aai.sparky.viewandinspect.sync;
+
+import org.onap.aai.sparky.config.oxm.OxmEntityContainerLookup;
+import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
+import org.onap.aai.sparky.config.oxm.SearchableEntityLookup;
+import org.onap.aai.sparky.dal.ElasticSearchAdapter;
+import org.onap.aai.sparky.dal.GizmoAdapter;
+import org.onap.aai.sparky.sync.ElasticSearchIndexCleaner;
+import org.onap.aai.sparky.sync.ElasticSearchSchemaFactory;
+import org.onap.aai.sparky.sync.IndexCleaner;
+import org.onap.aai.sparky.sync.IndexIntegrityValidator;
+import org.onap.aai.sparky.sync.SyncControllerImpl;
+import org.onap.aai.sparky.sync.SyncControllerRegistrar;
+import org.onap.aai.sparky.sync.SyncControllerRegistry;
+import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig;
+import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig;
+import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig;
+import org.onap.aai.sparky.sync.config.SyncControllerConfig;
+
+public class ViewInspectGizmoSyncController extends SyncControllerImpl
+    implements SyncControllerRegistrar {
+
+  private SyncControllerRegistry syncControllerRegistry;
+  //private GizmoAdapter gizmoAdapter;
+  //private ElasticSearchAdapter esAdapter;
+  //private ElasticSearchSchemaConfig schemaConfig;
+  //private ElasticSearchEndpointConfig endpointConfig;
+
+  public ViewInspectGizmoSyncController(SyncControllerConfig syncControllerConfig,
+      GizmoAdapter gizmoAdapter, ElasticSearchAdapter esAdapter,
+      ElasticSearchSchemaConfig schemaConfig, ElasticSearchEndpointConfig endpointConfig,
+      NetworkStatisticsConfig gizmoStatConfig, NetworkStatisticsConfig esStatConfig,
+      OxmEntityLookup oxmEntityLookup,
+      SearchableEntityLookup searchableEntityLookup, OxmEntityContainerLookup oxmEntityContainerLookup) throws Exception {
+    super(syncControllerConfig);
+
+    // final String controllerName = "View and Inspect Entity Synchronizer";
+
+     //this.gizmoAdapter = gizmoAdapter; 
+     //this.esAdapter = esAdapter; 
+     //this.schemaConfig = schemaConfig; 
+     //this.endpointConfig = endpointConfig; 
+     
+    IndexIntegrityValidator indexValidator = new IndexIntegrityValidator(esAdapter, schemaConfig,
+        endpointConfig, ElasticSearchSchemaFactory.getIndexSchema(schemaConfig));
+
+    registerIndexValidator(indexValidator);
+
+    ViewInspectGizmoEntitySynchronizer ses = new ViewInspectGizmoEntitySynchronizer(schemaConfig,
+        syncControllerConfig.getNumInternalSyncWorkers(),
+        syncControllerConfig.getNumSyncActiveInventoryWorkers(),
+        syncControllerConfig.getNumSyncElasticWorkers(), gizmoStatConfig, esStatConfig,
+        oxmEntityLookup, searchableEntityLookup, oxmEntityContainerLookup);
+    
+    ses.setGizmoAdapter(gizmoAdapter);
+    ses.setElasticSearchAdapter(esAdapter);
+
+    registerEntitySynchronizer(ses);
+
+    IndexCleaner indexCleaner =
+        new ElasticSearchIndexCleaner(esAdapter, endpointConfig, schemaConfig);
+
+    registerIndexCleaner(indexCleaner);
+
+  }
+
+  public SyncControllerRegistry getSyncControllerRegistry() {
+    return syncControllerRegistry;
+  }
+
+  public void setSyncControllerRegistry(SyncControllerRegistry syncControllerRegistry) {
+    this.syncControllerRegistry = syncControllerRegistry;
+  }
+  
+  @Override
+  public void registerController() {
+    if ( syncControllerRegistry != null ) {
+      if ( syncControllerConfig.isEnabled()) { 
+        syncControllerRegistry.registerSyncController(this);
+      }
+    }
+
+  }
+}