/** * Copyright 2017 Pinterest, Inc. * * 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. */ package com.pinterest.soundwave.resources; import com.pinterest.OperationStats; import com.pinterest.soundwave.api.EsAggregation; import com.pinterest.soundwave.api.EsQuery; import com.pinterest.soundwave.bean.EsQueryResult; import com.pinterest.soundwave.elasticsearch.CmdbInstanceStore; import com.pinterest.soundwave.utils.Utils; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.validation.constraints.NotNull; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @Path("/v2/") @Produces(MediaType.APPLICATION_JSON) @Api() public class Query { private static final Logger logger = LoggerFactory.getLogger(Query.class); private static final List<String> dateFieldNames = new ArrayList<>(Arrays.asList( "terminated_time", "launch_time", "launchTime", "aws_launch_time", "created_time", "updated_time")); private static ObjectMapper mapper = new ObjectMapper(); private CmdbInstanceStore cmdbInstanceStore; public Query(CmdbInstanceStore cmdbInstanceStore) { this.cmdbInstanceStore = cmdbInstanceStore; } @POST @Consumes(MediaType.APPLICATION_JSON) @Path("/query") @ApiOperation( value = "Send a general query", response = Map.class, responseContainer = "List") public Response query(EsQuery esQuery) { OperationStats opStats = new OperationStats("cmdb_api", "query", new HashMap<>()); Map<String, String> tags = new HashMap<>(); String queryString = esQuery.getQueryString(); String fields = esQuery.getFields(); try { // Metrics tags tags.put("status", String.valueOf(Response.Status.OK.getStatusCode())); opStats.succeed(tags); logger.info("Success: query - {}", queryString); String[] includeFields = StringUtils.split(fields, ","); Iterator<EsQueryResult> output = cmdbInstanceStore.query(queryString, includeFields); // Process iterator items one by one instead of converting to list List<Map<String, Object>> results = generateFlatOutput(output, includeFields); return Response.status(Response.Status.OK) .type(MediaType.APPLICATION_JSON) .entity(results) .build(); } catch (Exception e) { return Utils.responseException(e, logger, opStats, tags); } // End of query function } /** * This function processes curl request made without using a json header * * @param text String format of the data * @return Response object */ @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Path("/query") public Response textQuery(String text) { try { String input = URLDecoder.decode(text, "UTF-8"); EsQuery esQuery = mapper.readValue(input, EsQuery.class); return query(esQuery); } catch (Exception e) { OperationStats opStats = new OperationStats("cmdb_api", "query", new HashMap<>()); Map<String, String> tags = new HashMap<>(); logger.error("Error in processing the input string. Cannot map to esQuery.class"); return Utils.responseException(e, logger, opStats, tags); } // End of text query function } @POST @Consumes(MediaType.APPLICATION_JSON) @Path("/aggregations/terms") public Response getAggregations(EsAggregation esAggregation) { Preconditions.checkNotNull(esAggregation); OperationStats opStats = new OperationStats("cmdb_api", "get_aggregations", new HashMap<>()); Map<String, String> tags = new HashMap<>(); String query = esAggregation.getQuery(); try { String[] aggregationParams = StringUtils.split(query, ","); if (aggregationParams.length == 0) { logger.warn("Aggregation Request with no fields"); tags.put("status", String.valueOf(Response.Status.BAD_REQUEST.getStatusCode())); return Response.status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON) .build(); } // Metrics tags tags.put("status", String.valueOf(Response.Status.OK.getStatusCode())); opStats.succeed(tags); logger.info("Success: aggregation - {}", query); List<String> aggregationParamsList = Arrays.asList(aggregationParams); Map<String, HashMap> results = cmdbInstanceStore.getAggregations(aggregationParamsList); return Response.status(Response.Status.OK) .type(MediaType.APPLICATION_JSON) .entity(results) .build(); } catch (Exception e) { return Utils.responseException(e, logger, opStats, tags); } } @GET @Path("/aggregations/terms/{queryString}") public Response getAggregationsUrl(@PathParam("queryString") @NotNull String query) { EsAggregation aggregation = new EsAggregation(); aggregation.setQuery(query); return getAggregations(aggregation); } List<Map<String, Object>> generateFlatOutput(Iterator<EsQueryResult> esQueryResults, String[] includeFields) { List<Map<String, Object>> flatResults = new ArrayList<>(); while (esQueryResults.hasNext()) { EsQueryResult esQueryResult = esQueryResults.next(); Map<String, Object> data = new HashMap<>(); // For each user requested field for (String fieldName : includeFields) { // Hierarchy if (StringUtils.contains(fieldName, ".")) { Object value = processHierarchy(fieldName, esQueryResult); if (dateFieldNames.contains(fieldName)) { if (value != null) { DateTime dateTime = new DateTime(value, DateTimeZone.UTC); data.put(fieldName, dateTime.toString()); } else { // If any date is null set it to an empty string data.put(fieldName, ""); } } else { data.put(fieldName, value); } } else { // Simple field if (esQueryResult.containsKey(fieldName)) { // If key is present Object value = esQueryResult.get(fieldName); if (dateFieldNames.contains(fieldName)) { if (value != null) { DateTime dateTime = new DateTime(value, DateTimeZone.UTC); data.put(fieldName, dateTime.toString()); } else { // If any date is null set it to an empty string data.put(fieldName, ""); } } else { data.put(fieldName, value); } } else { // Field name not in response data.put(fieldName, ""); } } } flatResults.add(data); } return flatResults; } Object processHierarchy(String fieldName, EsQueryResult esQueryResult) { Object outputValue = ""; String[] propertyNames = StringUtils.split(fieldName, "."); Map<String, Object> inputData = esQueryResult; for (int i = 0; i < propertyNames.length; i++) { if (inputData.containsKey(propertyNames[i])) { // input data contains the key if (i == (propertyNames.length - 1)) { // Last in hierarchy outputValue = inputData.get(propertyNames[i]); break; } else { inputData = (Map<String, Object>) inputData.get(propertyNames[i]); } } else { outputValue = ""; break; } } return outputValue; } // End of class }