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