Adding UI extensibility
[aai/sparky-be.git] / src / main / java / org / onap / aai / sparky / search / EntityCountHistoryProcessor.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 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  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.onap.aai.sparky.search;
24
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.TreeMap;
31
32 import org.apache.camel.Exchange;
33 import org.apache.camel.Processor;
34 import org.apache.camel.component.restlet.RestletConstants;
35 import org.json.JSONArray;
36 import org.json.JSONObject;
37 import org.onap.aai.cl.api.Logger;
38 import org.onap.aai.cl.eelf.LoggerFactory;
39 import org.onap.aai.cl.mdc.MdcContext;
40 import org.onap.aai.restclient.client.OperationResult;
41 import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter;
42 import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig;
43 import org.onap.aai.sparky.inventory.EntityHistoryQueryBuilder;
44 import org.onap.aai.sparky.logging.AaiUiMsgs;
45 import org.onap.aai.sparky.logging.util.ServletUtils;
46 import org.onap.aai.sparky.util.NodeUtils;
47 import org.onap.aai.sparky.util.RestletUtils;
48 import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs;
49 import org.restlet.Request;
50 import org.restlet.Response;
51 import org.restlet.data.ClientInfo;
52 import org.restlet.data.MediaType;
53 import org.restlet.data.Status;
54
55 import com.fasterxml.jackson.core.JsonProcessingException;
56 import com.fasterxml.jackson.databind.JsonNode;
57 import com.fasterxml.jackson.databind.ObjectMapper;
58 import com.fasterxml.jackson.databind.SerializationFeature;
59
60 /**
61  * Receives and processes Entity Count History requests
62  */
63 public class EntityCountHistoryProcessor implements Processor {
64
65   private static final Logger LOG =
66       LoggerFactory.getInstance().getLogger(EntityCountHistoryProcessor.class);
67
68   private static final long serialVersionUID = 1L;
69
70   private SearchAdapter search = null;
71   private ElasticSearchConfig elasticConfig = null;
72   private VisualizationConfigs visualConfigs = null;
73   private ObjectMapper mapper;
74
75   private static final String SEARCH_STRING = "_search";
76   private static final String TYPE = "type";
77   private static final String TABLE = "table";
78   private static final String GRAPH = "graph";
79
80   private List<String> vnfEntityTypesToSummarize;
81   private boolean summarizevnf = false;
82
83   private RestletUtils restletUtils = new RestletUtils();
84
85   /**
86    * Instantiates a new Entity Count History
87    */
88
89   public EntityCountHistoryProcessor(VisualizationConfigs visualizationConfigs) {
90
91     this.visualConfigs = visualizationConfigs;
92     vnfEntityTypesToSummarize =
93         Arrays.asList(visualConfigs.getVnfEntityTypes().toLowerCase().split("[\\s,]+"));
94     summarizevnf = visualConfigs.getEntityTypesToSummarize().toLowerCase().contains("vnf");
95     try {
96       if (elasticConfig == null) {
97         elasticConfig = ElasticSearchConfig.getConfig();
98       }
99
100       if (search == null) {
101         search = new SearchAdapter();
102       }
103       this.mapper = new ObjectMapper();
104       this.mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
105     } catch (Exception exc) {
106       LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, exc.getLocalizedMessage());
107     }
108   }
109
110   /**
111    * Processes a entity count history search request
112    *
113    * @param exchange The Exchange object generated by Apache Camel for the incoming request
114    */
115
116   @Override
117   public void process(Exchange exchange) throws Exception {
118
119     Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class);
120     Response restletResponse =
121         exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class);
122
123     Object xTransactionId = exchange.getIn().getHeader("X-TransactionId");
124     if (xTransactionId == null) {
125       xTransactionId = NodeUtils.getRandomTxnId();
126     }
127
128     Object partnerName = exchange.getIn().getHeader("X-FromAppId");
129     if (partnerName == null) {
130       partnerName = "Browser";
131     }
132
133     /*
134      * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log
135      * entry which includes client (e.g. browser) information
136      */
137     request.setLoggable(false);
138
139     ClientInfo clientInfo = request.getClientInfo();
140     MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName,
141         clientInfo.getAddress() + ":" + clientInfo.getPort());
142
143     String typeParameter = getTypeParameter(exchange);
144
145     if (null != typeParameter && !typeParameter.isEmpty()) {
146       OperationResult operationResult = null;
147
148       try {
149         operationResult = getResults(restletResponse, typeParameter);
150         restletResponse.setEntity(operationResult.getResult(), MediaType.APPLICATION_JSON);
151       } catch (Exception exc) {
152         LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, exc.getLocalizedMessage());
153       }
154     } else {
155       LOG.error(AaiUiMsgs.RESOURCE_NOT_FOUND, request.getOriginalRef().toString());
156       String errorMessage =
157           restletUtils.generateJsonErrorResponse("Unsupported request. Resource not found.");
158       restletResponse.setEntity(errorMessage, MediaType.APPLICATION_JSON);
159       restletResponse.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
160     }
161
162     exchange.getOut().setBody(restletResponse);
163   }
164
165
166   /**
167    * Format line graph output
168    *
169    * @param results The results
170    * @return The JSON object
171    * @throws JsonProcessingException The JSON processing exception
172    */
173   public JSONObject formatLineGraphOutput(String results) throws JsonProcessingException {
174     Map<Long, Long> countByDateMap = new HashMap<Long, Long>();
175
176     JsonNode resultNode = null;
177
178     JSONObject finalResult = new JSONObject();
179     JSONArray finalResultArr = new JSONArray();
180
181     try {
182       resultNode = mapper.readTree(results);
183
184       final JsonNode bucketsNode = getBucketsNode(resultNode);
185
186       if (bucketsNode.isArray()) {
187
188         for (final JsonNode entityNode : bucketsNode) {
189           final JsonNode dateBucketNode = entityNode.get("group_by_date").get("buckets");
190           if (dateBucketNode.isArray()) {
191             for (final JsonNode dateBucket : dateBucketNode) {
192               Long date = dateBucket.get("key").asLong();
193               final JsonNode countBucketNode =
194                   dateBucket.get("sort_by_date").get("hits").get("hits");
195
196               if (countBucketNode.isArray()) {
197                 final JsonNode latestEntityNode = countBucketNode.get(0);
198
199                 long currentCount = latestEntityNode.get("_source").get("count").asLong();
200                 if (countByDateMap.containsKey(date)) {
201                   // add to the value if map already contains this date
202                   currentCount += countByDateMap.get(date);
203                 }
204
205                 countByDateMap.put(date, currentCount);
206               }
207             }
208
209           }
210         }
211       }
212
213       /*
214        * Sort the map by epoch timestamp
215        */
216       Map<Long, Long> sortedMap = new TreeMap<Long, Long>(countByDateMap);
217       for (Entry<Long, Long> entry : sortedMap.entrySet()) {
218         JSONObject dateEntry = new JSONObject();
219         dateEntry.put("date", entry.getKey());
220         dateEntry.put("count", entry.getValue());
221         finalResultArr.put(dateEntry);
222       }
223
224     } catch (Exception exc) {
225       LOG.warn(AaiUiMsgs.ERROR_BUILDING_SEARCH_RESPONSE, exc.getLocalizedMessage());
226     }
227
228     return finalResult.put("result", finalResultArr);
229   }
230
231   /**
232    * Format table output
233    *
234    * @param results The results
235    * @return The JSON object
236    * @throws JsonProcessingException The JSON processing exception
237    */
238   public JSONObject formatTableOutput(String results) throws JsonProcessingException {
239     JsonNode resultNode = null;
240
241     JSONObject finalResult = new JSONObject();
242     JSONArray entitiesArr = new JSONArray();
243
244     Map<String, Long> entityCountInTable = initializeEntityMap();
245
246     long vnfCount = 0;
247
248     try {
249       resultNode = mapper.readTree(results);
250
251       final JsonNode bucketsNode = getBucketsNode(resultNode);
252       if (bucketsNode.isArray()) {
253
254         for (final JsonNode entityNode : bucketsNode) {
255           String entityType = entityNode.get("key").asText();
256           boolean isAVnf = vnfEntityTypesToSummarize.contains(entityType);
257           long countValue = 0;
258
259           if (isAVnf || entityCountInTable.get(entityType) != null) {
260             final JsonNode hitsBucketNode = entityNode.get("sort_by_date").get("hits").get("hits");
261             if (hitsBucketNode.isArray()) {
262               // the first bucket will be the latest
263               final JsonNode hitNode = hitsBucketNode.get(0);
264
265               countValue = hitNode.get("_source").get("count").asLong();
266
267               /*
268                * Special case: Add all the VNF types together to get aggregate count
269                */
270               if (summarizevnf && isAVnf) {
271                 vnfCount += countValue;
272                 countValue = vnfCount;
273                 entityType = "vnf";
274               }
275
276               entityCountInTable.replace(entityType, countValue);
277             }
278           }
279
280         }
281       }
282       for (Entry<String, Long> entry : entityCountInTable.entrySet()) {
283         JSONObject entityType = new JSONObject();
284         entityType.put("key", entry.getKey());
285         entityType.put("doc_count", entry.getValue());
286         entitiesArr.put(entityType);
287       }
288
289       finalResult.put("result", entitiesArr);
290
291     } catch (Exception exc) {
292       LOG.warn(AaiUiMsgs.ERROR_BUILDING_RESPONSE_FOR_TABLE_QUERY, exc.getLocalizedMessage());
293     }
294
295     return finalResult;
296   }
297
298   /**
299    * Gets the results
300    *
301    * @param response The response
302    * @param type The type
303    * @return The results
304    */
305   public OperationResult getResults(Response response, String type) {
306     OperationResult operationResult = new OperationResult();
307
308     String requestString =
309         String.format("/%s/%s?pretty", elasticConfig.getEntityCountHistoryIndex(), SEARCH_STRING);
310
311     String reqPayload = EntityHistoryQueryBuilder.getQuery(type).toString();
312
313     try {
314       final String fullUrlStr = ServletUtils.getFullUrl(elasticConfig, requestString);
315       OperationResult opResult =
316           restletUtils.executePostQuery(LOG, search, response, fullUrlStr, reqPayload);
317
318       JSONObject finalOutput = null;
319       if (type.equalsIgnoreCase(TABLE)) {
320         finalOutput = formatTableOutput(opResult.getResult());
321       } else if (type.equalsIgnoreCase(GRAPH)) {
322         finalOutput = formatLineGraphOutput(opResult.getResult());
323       }
324
325       if (finalOutput != null) {
326         response.setEntity(finalOutput.toString(), MediaType.APPLICATION_JSON);
327         operationResult.setResult(finalOutput.toString());
328       }
329     } catch (JsonProcessingException exc) {
330       restletUtils.handleRestletErrors(LOG, "Unable to map JSONpayload", exc, response);
331     }
332
333     return operationResult;
334   }
335
336   /**
337    * Gets the buckets node
338    *
339    * @param node The node
340    * @return The buckets node
341    * @throws Exception The exception
342    */
343   public JsonNode getBucketsNode(JsonNode node) throws Exception {
344     if (node.get("aggregations").get("group_by_entityType").get("buckets") != null) {
345       return node.get("aggregations").get("group_by_entityType").get("buckets");
346     } else {
347       throw new Exception("Failed to map JSON response");
348     }
349   }
350
351   /**
352    * Initialize entity map
353    *
354    * @return the map
355    */
356   private Map<String, Long> initializeEntityMap() {
357     Map<String, Long> entityMap = new HashMap<String, Long>();
358     String[] entityTypes = visualConfigs.getEntityTypesToSummarize().split(",");
359     for (String entity : entityTypes) {
360       entityMap.put(entity, (long) 0);
361     }
362
363     return entityMap;
364   }
365
366   /**
367    * Extracts the "type" query parameter from the request URI
368    *
369    * @param exchange
370    * @return String containing the value of the "type" query parameter of the request. Returns null
371    *         if no "type" parameter found
372    */
373   public String getTypeParameter(Exchange exchange) {
374     String typeParameter = null;
375
376     String requestUriParameterString = exchange.getIn().getHeader("CamelHttpQuery", String.class);
377
378     if (null != requestUriParameterString) {
379       String[] requestParameterParts = requestUriParameterString.split("&");
380
381       String[] parameter = requestParameterParts[0].split("=");
382       String currentParameterKey = parameter[0];
383
384       if (null != currentParameterKey && !currentParameterKey.isEmpty()) {
385         // Check if we're looking at the "type" parameter key
386         if (currentParameterKey.equals(TYPE)) {
387           boolean uriIncludesTypeParameterValue =
388               (parameter.length >= 2) && !parameter[1].isEmpty();
389
390           if (uriIncludesTypeParameterValue) {
391             String typeParameterValue = parameter[1];
392
393             // Is the parameter value one that we return data for?
394             if (typeParameterValue.equalsIgnoreCase(TABLE)
395                 || typeParameterValue.equalsIgnoreCase(GRAPH)) {
396               typeParameter = typeParameterValue;
397             }
398           }
399         }
400       }
401     }
402
403     return typeParameter;
404   }
405
406   public void setElasticConfig(ElasticSearchConfig elasticConfig) {
407     this.elasticConfig = elasticConfig;
408   }
409
410   public void setRestletUtils(RestletUtils restletUtils) {
411     this.restletUtils = restletUtils;
412   }
413
414   public void setSearch(SearchAdapter search) {
415     this.search = search;
416   }
417 }