/** * ============LICENSE_START======================================================= * org.onap.aai * ================================================================================ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. * Copyright © 2017-2018 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========================================================= */ package org.onap.aai.sparky.search; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.component.restlet.RestletConstants; import org.json.JSONArray; import org.json.JSONObject; 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.dal.ElasticSearchAdapter; import org.onap.aai.sparky.inventory.EntityHistoryQueryBuilder; import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.sparky.util.NodeUtils; import org.onap.aai.sparky.util.RestletUtils; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.ClientInfo; import org.restlet.data.MediaType; import org.restlet.data.Status; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; /** * Receives and processes Entity Count History requests */ public class EntityCountHistoryProcessor implements Processor { private static final Logger LOG = LoggerFactory.getInstance().getLogger(EntityCountHistoryProcessor.class); private static final long serialVersionUID = 1L; private ElasticSearchAdapter elasticSearchAdapter = null; private ObjectMapper mapper; private static final String SEARCH_PRETTY_STRING = "_search?pretty"; private static final String TYPE = "type"; private static final String TABLE = "table"; private static final String GRAPH = "graph"; private List entityTypesToSummarize; private List vnfEntityTypes; private String entityCountHistoryIndexName; private boolean summarizeVnfs = false; private RestletUtils restletUtils = new RestletUtils(); /** * Instantiates a new Entity Count History */ public EntityCountHistoryProcessor(ElasticSearchAdapter elasticSearchAdapter, String entityTypesToSummarizeDelimitedList, String vnfEntityTypesDelimitedList, String entityCountHistoryIndexName) { this.elasticSearchAdapter = elasticSearchAdapter; this.entityCountHistoryIndexName = entityCountHistoryIndexName; entityTypesToSummarize = Arrays.asList(entityTypesToSummarizeDelimitedList.toLowerCase().split("[\\s,]+")); vnfEntityTypes = Arrays.asList(vnfEntityTypesDelimitedList.toLowerCase().split("[\\s,]+")); summarizeVnfs = vnfEntityTypesDelimitedList.toLowerCase().contains("vnf"); this.mapper = new ObjectMapper(); this.mapper.configure(SerializationFeature.INDENT_OUTPUT, true); } /** * Processes a entity count history search request * * @param exchange The Exchange object generated by Apache Camel for the incoming request */ @Override public void process(Exchange exchange) throws Exception { Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class); Response restletResponse = exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class); Object xTransactionId = exchange.getIn().getHeader("X-TransactionId"); if (xTransactionId == null) { xTransactionId = NodeUtils.getRandomTxnId(); } Object partnerName = exchange.getIn().getHeader("X-FromAppId"); if (partnerName == null) { partnerName = "Browser"; } /* * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log * entry which includes client (e.g. browser) information */ request.setLoggable(false); ClientInfo clientInfo = request.getClientInfo(); MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName, clientInfo.getAddress() + ":" + clientInfo.getPort()); String typeParameter = getTypeParameter(exchange); if (null != typeParameter && !typeParameter.isEmpty()) { OperationResult operationResult = null; try { operationResult = getResults(restletResponse, typeParameter); restletResponse.setEntity(operationResult.getResult(), MediaType.APPLICATION_JSON); } catch (Exception exc) { LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, exc.getLocalizedMessage()); } } else { LOG.error(AaiUiMsgs.RESOURCE_NOT_FOUND, request.getOriginalRef().toString()); String errorMessage = restletUtils.generateJsonErrorResponse("Unsupported request. Resource not found."); restletResponse.setEntity(errorMessage, MediaType.APPLICATION_JSON); restletResponse.setStatus(Status.CLIENT_ERROR_NOT_FOUND); } exchange.getOut().setBody(restletResponse); } /** * Format line graph output * * @param results The results * @return The JSON object * @throws JsonProcessingException The JSON processing exception */ public JSONObject formatLineGraphOutput(String results) throws JsonProcessingException { Map countByDateMap = new HashMap(); JsonNode resultNode = null; JSONObject finalResult = new JSONObject(); JSONArray finalResultArr = new JSONArray(); try { resultNode = mapper.readTree(results); final JsonNode bucketsNode = getBucketsNode(resultNode); if (bucketsNode.isArray()) { for (final JsonNode entityNode : bucketsNode) { final JsonNode dateBucketNode = entityNode.get("group_by_date").get("buckets"); if (dateBucketNode.isArray()) { for (final JsonNode dateBucket : dateBucketNode) { Long date = dateBucket.get("key").asLong(); final JsonNode countBucketNode = dateBucket.get("sort_by_date").get("hits").get("hits"); if (countBucketNode.isArray()) { final JsonNode latestEntityNode = countBucketNode.get(0); long currentCount = latestEntityNode.get("_source").get("count").asLong(); if (countByDateMap.containsKey(date)) { // add to the value if map already contains this date currentCount += countByDateMap.get(date); } countByDateMap.put(date, currentCount); } } } } } /* * Sort the map by epoch timestamp */ Map sortedMap = new TreeMap(countByDateMap); for (Entry entry : sortedMap.entrySet()) { JSONObject dateEntry = new JSONObject(); dateEntry.put("date", entry.getKey()); dateEntry.put("count", entry.getValue()); finalResultArr.put(dateEntry); } } catch (Exception exc) { LOG.warn(AaiUiMsgs.ERROR_BUILDING_SEARCH_RESPONSE, exc.getLocalizedMessage()); } return finalResult.put("result", finalResultArr); } /** * Format table output * * @param results The results * @return The JSON object * @throws JsonProcessingException The JSON processing exception */ public JSONObject formatTableOutput(String results) throws JsonProcessingException { JsonNode resultNode = null; JSONObject finalResult = new JSONObject(); JSONArray entitiesArr = new JSONArray(); Map entityCountInTable = initializeEntityMap(); long vnfCount = 0; try { resultNode = mapper.readTree(results); final JsonNode bucketsNode = getBucketsNode(resultNode); if (bucketsNode.isArray()) { for (final JsonNode entityNode : bucketsNode) { String entityType = entityNode.get("key").asText(); boolean isAVnf = vnfEntityTypes.contains(entityType); long countValue = 0; if (isAVnf || entityCountInTable.get(entityType) != null) { final JsonNode hitsBucketNode = entityNode.get("sort_by_date").get("hits").get("hits"); if (hitsBucketNode.isArray()) { // the first bucket will be the latest final JsonNode hitNode = hitsBucketNode.get(0); countValue = hitNode.get("_source").get("count").asLong(); /* * Special case: Add all the VNF types together to get aggregate count */ if (summarizeVnfs && isAVnf) { vnfCount += countValue; countValue = vnfCount; entityType = "vnf"; } entityCountInTable.replace(entityType, countValue); } } } } for (Entry entry : entityCountInTable.entrySet()) { JSONObject entityType = new JSONObject(); entityType.put("key", entry.getKey()); entityType.put("doc_count", entry.getValue()); entitiesArr.put(entityType); } finalResult.put("result", entitiesArr); } catch (Exception exc) { LOG.warn(AaiUiMsgs.ERROR_BUILDING_RESPONSE_FOR_TABLE_QUERY, exc.getLocalizedMessage()); } return finalResult; } /** * Gets the results * * @param response The response * @param type The type * @return The results */ public OperationResult getResults(Response response, String type) { OperationResult operationResult = new OperationResult(); String reqPayload = EntityHistoryQueryBuilder.getQuery(type).toString(); try { final String fullUrlStr = elasticSearchAdapter .buildElasticSearchUrlForApi(entityCountHistoryIndexName, SEARCH_PRETTY_STRING); OperationResult opResult = elasticSearchAdapter.doPost(fullUrlStr, reqPayload, javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE); JSONObject finalOutput = null; if (type.equalsIgnoreCase(TABLE)) { finalOutput = formatTableOutput(opResult.getResult()); } else if (type.equalsIgnoreCase(GRAPH)) { finalOutput = formatLineGraphOutput(opResult.getResult()); } if (finalOutput != null) { response.setEntity(finalOutput.toString(), MediaType.APPLICATION_JSON); operationResult.setResult(finalOutput.toString()); } } catch (JsonProcessingException exc) { restletUtils.handleRestletErrors(LOG, "Unable to map JSONpayload", exc, response); } return operationResult; } /** * Gets the buckets node * * @param node The node * @return The buckets node * @throws Exception The exception */ public JsonNode getBucketsNode(JsonNode node) throws Exception { if (node.get("aggregations").get("group_by_entityType").get("buckets") != null) { return node.get("aggregations").get("group_by_entityType").get("buckets"); } else { throw new Exception("Failed to map JSON response"); } } /** * Initialize entity map * * @return the map */ private Map initializeEntityMap() { Map entityMap = new HashMap(); for (String entity : entityTypesToSummarize) { entityMap.put(entity, (long) 0); } return entityMap; } /** * Extracts the "type" query parameter from the request URI * * @param exchange * @return String containing the value of the "type" query parameter of the request. Returns null * if no "type" parameter found */ public String getTypeParameter(Exchange exchange) { String typeParameter = null; String requestUriParameterString = exchange.getIn().getHeader("CamelHttpQuery", String.class); if (null != requestUriParameterString) { String[] requestParameterParts = requestUriParameterString.split("&"); String[] parameter = requestParameterParts[0].split("="); String currentParameterKey = parameter[0]; if (null != currentParameterKey && !currentParameterKey.isEmpty()) { // Check if we're looking at the "type" parameter key if (currentParameterKey.equals(TYPE)) { boolean uriIncludesTypeParameterValue = (parameter.length >= 2) && !parameter[1].isEmpty(); if (uriIncludesTypeParameterValue) { String typeParameterValue = parameter[1]; // Is the parameter value one that we return data for? if (typeParameterValue.equalsIgnoreCase(TABLE) || typeParameterValue.equalsIgnoreCase(GRAPH)) { typeParameter = typeParameterValue; } } } } } return typeParameter; } public void setRestletUtils(RestletUtils restletUtils) { this.restletUtils = restletUtils; } }