implemented sparky graph extensibility
[aai/sparky-be.git] / sparkybe-onap-service / src / main / java / org / onap / aai / sparky / viewandinspect / services / BaseVisualizationService.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017-2018 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21 package org.onap.aai.sparky.viewandinspect.services;
22
23 import java.io.IOException;
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentHashMap;
26
27 import javax.servlet.ServletException;
28
29 import org.onap.aai.cl.api.Logger;
30 import org.onap.aai.cl.eelf.LoggerFactory;
31 import org.onap.aai.restclient.client.OperationResult;
32 import org.onap.aai.sparky.dal.rest.config.RestEndpointConfig;
33 import org.onap.aai.sparky.logging.AaiUiMsgs;
34 import org.onap.aai.sparky.search.SearchServiceAdapter;
35 import org.onap.aai.sparky.subscription.config.SubscriptionConfig;
36 import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig;
37 import org.onap.aai.sparky.sync.entity.SearchableEntity;
38 import org.onap.aai.sparky.util.NodeUtils;
39 import org.onap.aai.sparky.viewandinspect.VisualizationContext;
40 import org.onap.aai.sparky.viewandinspect.VisualizationContextBuilder;
41 import org.onap.aai.sparky.viewandinspect.VisualizationService;
42 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
43 import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode;
44 import org.onap.aai.sparky.viewandinspect.entity.D3VisualizationOutput;
45 import org.onap.aai.sparky.viewandinspect.entity.GraphMeta;
46 import org.onap.aai.sparky.viewandinspect.entity.QueryParams;
47 import org.onap.aai.sparky.viewandinspect.entity.QueryRequest;
48
49 import com.fasterxml.jackson.annotation.JsonInclude.Include;
50 import com.fasterxml.jackson.core.JsonProcessingException;
51 import com.fasterxml.jackson.databind.DeserializationFeature;
52 import com.fasterxml.jackson.databind.JsonNode;
53 import com.fasterxml.jackson.databind.ObjectMapper;
54
55 public class BaseVisualizationService implements VisualizationService {
56   
57   private static final Logger LOG =
58       LoggerFactory.getInstance().getLogger(BaseVisualizationService.class);
59   
60   protected ObjectMapper mapper = new ObjectMapper();
61
62   protected final SearchServiceAdapter searchServiceAdapter;
63   
64   protected ConcurrentHashMap<Long, VisualizationContext> contextMap;
65
66   protected VisualizationConfigs visualizationConfigs;
67   protected SubscriptionConfig subConfig;
68   protected RestEndpointConfig endpointConfig;
69   protected ElasticSearchSchemaConfig schemaEConfig;
70   
71   protected VisualizationContextBuilder contextBuilder;
72   
73   public BaseVisualizationService(VisualizationContextBuilder contextBuilder,
74       VisualizationConfigs visualizationConfigs, SearchServiceAdapter searchServiceAdapter,
75       RestEndpointConfig endpointConfig, ElasticSearchSchemaConfig schemaConfig,
76       SubscriptionConfig subscriptionConfig) throws Exception {
77    
78     this.visualizationConfigs = visualizationConfigs;
79     this.endpointConfig = endpointConfig; 
80     this.schemaEConfig = schemaConfig; 
81     this.subConfig = subscriptionConfig;
82     this.contextBuilder = contextBuilder;
83     
84     this.searchServiceAdapter = searchServiceAdapter; 
85
86     this.mapper = new ObjectMapper();
87     mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
88     
89     this.contextMap = new ConcurrentHashMap<Long, VisualizationContext>();
90     
91   }
92   
93   /**
94    * Analyze query request body.
95    *
96    * @param queryRequestJson the query request json
97    * @return the query request
98    */
99
100   public QueryRequest analyzeQueryRequestBody(String queryRequestJson) {
101
102
103     LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
104         "analyzeQueryRequestBody()," + " queryRequestJson = " + queryRequestJson);
105
106     ObjectMapper nonEmptyMapper = new ObjectMapper();
107     nonEmptyMapper.setSerializationInclusion(Include.NON_EMPTY);
108
109     QueryRequest queryBody = null;
110
111     try {
112       queryBody = nonEmptyMapper.readValue(queryRequestJson, QueryRequest.class);
113     } catch (Exception exc) {
114       LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT, "Analyzing query request body.",
115           exc.getLocalizedMessage());
116     }
117
118     return queryBody;
119
120   }
121   
122   /**
123    * Log optime.
124    *
125    * @param method the method
126    * @param opStartTimeInMs the op start time in ms
127    */
128   protected void logOptime(String method, long opStartTimeInMs) {
129     LOG.info(AaiUiMsgs.OPERATION_TIME, method,
130         String.valueOf(System.currentTimeMillis() - opStartTimeInMs));
131   }
132   
133   protected SearchableEntity extractSearchableEntityFromElasticEntity(OperationResult operationResult) {
134     if (operationResult == null || !operationResult.wasSuccessful()) {
135       // error, return empty collection
136       return null;
137     }
138  
139     SearchableEntity sourceEntity = null;
140     if (operationResult.wasSuccessful()) {
141
142       try {
143         JsonNode searchServiceResults = mapper.readValue(operationResult.getResult(), JsonNode.class);
144
145         if (searchServiceResults != null) {
146                 JsonNode sourceField = extractSearchServiceContent(searchServiceResults);
147
148           if (sourceField != null) {
149             sourceEntity = new SearchableEntity();
150             
151             String entityType = NodeUtils.extractFieldValueFromObject(sourceField, "entityType"); 
152             sourceEntity.setEntityType(entityType);  
153             String entityPrimaryKeyValue = NodeUtils.extractFieldValueFromObject(sourceField, "entityPrimaryKeyValue");
154             sourceEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
155             String link = NodeUtils.extractFieldValueFromObject(sourceField, "link"); 
156             sourceEntity.setLink(link);
157             String lastmodTimestamp = NodeUtils.extractFieldValueFromObject(sourceField, "lastmodTimestamp");
158             sourceEntity.setEntityTimeStamp(lastmodTimestamp);
159           }
160         }
161       } catch (IOException ioe) {
162         LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, "a json node ", ioe.getLocalizedMessage());
163       }
164     }
165     return sourceEntity;
166   }
167
168   /**
169    * Builds the visualization using generic query.
170    *
171    * @param queryRequest the query request
172    * @return the operation result
173    */
174   @Override
175   public OperationResult buildVisualization(QueryRequest queryRequest) {
176
177     OperationResult returnValue = new OperationResult();
178     OperationResult dataCollectionResult;
179     QueryParams queryParams = null;
180     SearchableEntity sourceEntity = null;
181
182     try {
183
184       /*
185        * Here is where we need to make a dip to elastic-search for the self-link by entity-id (link
186        * hash).
187        */
188         dataCollectionResult = searchServiceAdapter.retrieveEntityById(queryRequest.getHashId(), 
189                          schemaEConfig.getIndexName());
190         
191       sourceEntity = extractSearchableEntityFromElasticEntity(dataCollectionResult);
192
193       if (sourceEntity != null) {
194         sourceEntity.generateId();
195       }
196
197       queryParams = new QueryParams();
198       queryParams.setSearchTargetNodeId(queryRequest.getHashId());
199
200     } catch (Exception e1) {
201       LOG.error(AaiUiMsgs.FAILED_TO_GET_NODES_QUERY_RESULT, e1.getLocalizedMessage());
202       dataCollectionResult = new OperationResult(500, "Failed to get nodes-query result from AAI");
203     }
204
205     if (dataCollectionResult == null || dataCollectionResult.getResultCode() == 200) {
206
207       String d3OutputJsonOutput = null;
208
209       try {
210
211         d3OutputJsonOutput = getVisualizationOutputBasedonGenericQuery( sourceEntity, queryParams, queryRequest);
212
213         if (LOG.isDebugEnabled()) {
214           LOG.debug(AaiUiMsgs.DEBUG_GENERIC,
215               "Generated D3" + " output as json = " + d3OutputJsonOutput);
216         }
217
218         if (d3OutputJsonOutput != null) {
219           returnValue.setResultCode(200);
220           returnValue.setResult(d3OutputJsonOutput);
221         } else {
222           returnValue.setResult(500, "Failed to generate D3 graph visualization");
223         }
224
225       } catch (Exception exc) {
226         returnValue.setResult(500,
227             "Failed to generate D3 graph visualization, due to a servlet exception.");
228         LOG.error(AaiUiMsgs.ERROR_D3_GRAPH_VISUALIZATION, exc.getLocalizedMessage());
229       }
230     } else {
231       returnValue.setResult(dataCollectionResult.getResultCode(), dataCollectionResult.getResult());
232     }
233
234     return returnValue;
235
236   }
237   
238   
239   /**
240    * Gets the visualization output basedon generic query.
241    *
242    * @param searchtargetEntity entity that will be used to start visualization flow
243    * @param queryParams the query params
244    * @return the visualization output basedon generic query
245    * @throws ServletException the servlet exception
246    * @throws  
247    */
248   protected String getVisualizationOutputBasedonGenericQuery(SearchableEntity searchtargetEntity,
249       QueryParams queryParams, QueryRequest request) throws ServletException {
250
251     long opStartTimeInMs = System.currentTimeMillis();
252
253     VisualizationTransformer transformer = null;
254     try {
255       transformer = new VisualizationTransformer(visualizationConfigs, subConfig);
256     } catch (Exception exc) {
257       throw new ServletException(
258           "Failed to create VisualizationTransformer instance because of execption", exc);
259     }
260
261     VisualizationContext visContext = null;
262     
263     try {
264
265       visContext = contextBuilder.getVisualizationContext();
266       contextMap.putIfAbsent(visContext.getContextId(), visContext);
267
268     } catch (Exception e1) {
269       LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT,
270           "While building Visualization Context, " + e1.getLocalizedMessage());
271       throw new ServletException(e1);
272     }
273
274     long startTimeInMs = System.currentTimeMillis();
275
276     visContext.processSelfLinks(searchtargetEntity, queryParams);
277     contextMap.remove(visContext.getContextId());
278
279     logOptime("collectSelfLinkNodes()", startTimeInMs);
280
281     /*
282      * Flatten the graphs into a set of Graph and Link nodes. In this method I want the node graph
283      * resulting from the edge-tag-query to be represented first, and then we'll layer in
284      * relationship data.
285      */
286     long overlayDataStartTimeInMs = System.currentTimeMillis();
287
288     Map<String, ActiveInventoryNode> cachedNodeMap = visContext.getNodeCache();
289
290     if (LOG.isDebugEnabled()) {
291
292       StringBuilder sb = new StringBuilder(128);
293
294       sb.append("\nCached Node Map:\n");
295       for (String k : cachedNodeMap.keySet()) {
296         sb.append("\n----");
297         sb.append("\n").append(cachedNodeMap.get(k).dumpNodeTree(true));
298       }
299
300       LOG.debug(AaiUiMsgs.DEBUG_GENERIC, sb.toString());
301     }
302
303     GraphMeta graphMeta = new GraphMeta();
304     
305     transformer.buildFlatNodeArrayFromGraphCollection(cachedNodeMap, graphMeta);
306     transformer.buildLinksFromGraphCollection(cachedNodeMap);
307
308     /*
309      * - Apply configuration-driven styling
310      * - Build the final transformation response object
311      * - Use information we have to populate the GraphMeta object
312      */
313
314     transformer.addSearchTargetAttributesToRootNode();
315
316     D3VisualizationOutput output = getD3VisualizationOutput(opStartTimeInMs, transformer, graphMeta);
317
318     String jsonResponse = null;
319
320     if (output != null) {
321
322       try {
323         jsonResponse = transformer.convertVisualizationOutputToJson(output);
324       } catch (JsonProcessingException jpe) {
325         throw new ServletException(
326                 "Caught an exception while converting visualization output to json", jpe);
327       }
328     }
329
330     logOptime("[build flat node array, add relationship data, search target,"
331         + " color scheme, and generate visualization output]", overlayDataStartTimeInMs);
332     
333     logOptime("doFilter()", opStartTimeInMs);
334
335     return jsonResponse;
336
337   }
338
339   protected D3VisualizationOutput getD3VisualizationOutput(long opStartTimeInMs,
340       VisualizationTransformer transformer, GraphMeta graphMeta) throws ServletException {
341     D3VisualizationOutput output = null;
342     try {
343       output = transformer
344           .generateVisualizationOutput((System.currentTimeMillis() - opStartTimeInMs), graphMeta);
345     } catch (JsonProcessingException exc) {
346       throw new ServletException("Caught an exception while generation visualization output", exc);
347     } catch (IOException exc) {
348       LOG.error(AaiUiMsgs.FAILURE_TO_PROCESS_REQUEST, exc.getLocalizedMessage());
349     }
350     return output;
351   }
352   
353   protected JsonNode extractSearchServiceContent(JsonNode returnedData){
354                  
355           JsonNode searchResults = returnedData.get("searchResult");
356           JsonNode searchHits = searchResults.get("hits");
357           JsonNode searchDoc = searchHits.get(0).get("document");
358           JsonNode content = searchDoc.get("content");
359         
360          return content; 
361   }
362
363   @Override
364   public void shutdown() {
365     if ( contextBuilder != null ) {
366       contextBuilder.shutdown();
367     }
368   }
369   
370 }