All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.bard.webservice.data.HttpResponseMaker Maven / Gradle / Ivy

Go to download

Fili web service library provides core capabilities for RESTful aggregation navigation, query planning and metadata

There is a newer version: 1.1.13
Show newest version
// Copyright 2016 Yahoo Inc.
// Licensed under the terms of the Apache license. Please see LICENSE.md file distributed with this work for terms.
package com.yahoo.bard.webservice.data;

import static com.yahoo.bard.webservice.web.handlers.PartialDataRequestHandler.getPartialIntervalsWithDefault;
import static com.yahoo.bard.webservice.web.handlers.VolatileDataRequestHandler.getVolatileIntervalsWithDefault;
import static com.yahoo.bard.webservice.web.responseprocessors.ResponseContextKeys.API_METRIC_COLUMN_NAMES;
import static com.yahoo.bard.webservice.web.responseprocessors.ResponseContextKeys.HEADERS;
import static com.yahoo.bard.webservice.web.responseprocessors.ResponseContextKeys.PAGINATION_CONTEXT_KEY;
import static com.yahoo.bard.webservice.web.responseprocessors.ResponseContextKeys.PAGINATION_LINKS_CONTEXT_KEY;
import static com.yahoo.bard.webservice.web.responseprocessors.ResponseContextKeys.REQUESTED_API_DIMENSION_FIELDS;

import com.yahoo.bard.webservice.application.ObjectMappersSuite;
import com.yahoo.bard.webservice.data.dimension.Dimension;
import com.yahoo.bard.webservice.data.dimension.DimensionDictionary;
import com.yahoo.bard.webservice.data.dimension.DimensionField;
import com.yahoo.bard.webservice.druid.model.query.DruidQuery;
import com.yahoo.bard.webservice.util.Pagination;
import com.yahoo.bard.webservice.util.SimplifiedIntervalList;
import com.yahoo.bard.webservice.web.DefaultResponseFormatType;
import com.yahoo.bard.webservice.web.PreResponse;
import com.yahoo.bard.webservice.web.ResponseData;
import com.yahoo.bard.webservice.web.ResponseFormatType;
import com.yahoo.bard.webservice.web.ResponseWriter;
import com.yahoo.bard.webservice.web.apirequest.ApiRequest;
import com.yahoo.bard.webservice.web.apirequest.DataApiRequest;
import com.yahoo.bard.webservice.web.handlers.RequestHandlerUtils;
import com.yahoo.bard.webservice.web.responseprocessors.ResponseContext;
import com.yahoo.bard.webservice.web.util.ResponseUtils;

import java.net.URI;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.StreamingOutput;

/**
 * Translates a PreResponse into an HTTP Response containing the results of a query.
 */
@Singleton
public class HttpResponseMaker {

    protected final ObjectMappersSuite objectMappers;
    protected final DimensionDictionary dimensionDictionary;
    protected final ResponseWriter responseWriter;
    protected final ResponseUtils responseUtils;

    /**
     * Class constructor.
     *
     * @param objectMappers  Mappers object for serialization
     * @param dimensionDictionary  The dimension dictionary from which to look up dimensions by name
     * @param responseWriter  Serializer which takes responseData and apiRequest, outputs formatted data stream.
     * @param responseUtils A class providing utility methods for processing response headers.
     */
    @Inject
    public HttpResponseMaker(
            ObjectMappersSuite objectMappers,
            DimensionDictionary dimensionDictionary,
            ResponseWriter responseWriter,
            ResponseUtils responseUtils
    ) {
        this.objectMappers = objectMappers;
        this.dimensionDictionary = dimensionDictionary;
        this.responseWriter = responseWriter;
        this.responseUtils = responseUtils;
    }

    /**
     * Class constructor.
     *
     * @param objectMappers  Mappers object for serialization
     * @param dimensionDictionary  The dimension dictionary from which to look up dimensions by name
     * @param responseWriter  Serializer which takes responseData and apiRequest, outputs formatted data stream.
     */
    public HttpResponseMaker(
            ObjectMappersSuite objectMappers,
            DimensionDictionary dimensionDictionary,
            ResponseWriter responseWriter
    ) {
        this(objectMappers, dimensionDictionary, responseWriter, new ResponseUtils());
    }

    /**
     * Build complete response.
     *
     * @param preResponse  PreResponse object which contains result set, response context and headers
     * @param apiRequest  ApiRequest object which contains request related information
     * @param containerRequestContext The container for jersey request processing objects
     *
     * @return Completely built response with headers and result set
     */
    public javax.ws.rs.core.Response buildResponse(
            PreResponse preResponse,
            ApiRequest apiRequest,
            ContainerRequestContext containerRequestContext
    ) {
        ResponseBuilder rspBuilder = createResponseBuilder(
                preResponse.getResultSet(),
                preResponse.getResponseContext(),
                apiRequest,
                containerRequestContext
        );

        @SuppressWarnings("unchecked")
        MultivaluedMap headers = (MultivaluedMap) preResponse
                .getResponseContext()
                .get(HEADERS.getName());

        //Headers are a multivalued map, and we want to add each element of each value to the builder.
        headers.entrySet().stream()
                .forEach(entry -> entry.getValue().forEach(value -> rspBuilder.header(entry.getKey(), value)));

        return rspBuilder.build();
    }

    /**
     * Create a response builder with all the associated meta data.
     *
     * @param resultSet  The result set being processed
     * @param responseContext  A meta data container for the state gathered by the web container
     * @param apiRequest  ApiRequest object which contains request related information
     * @param containerRequestContext The container for jersey request processing objects
     *
     * @return Build response with requested format and associated meta data info.
     */
    @SuppressWarnings("unchecked")
    protected ResponseBuilder createResponseBuilder(
            ResultSet resultSet,
            ResponseContext responseContext,
            ApiRequest apiRequest,
            ContainerRequestContext containerRequestContext
    ) {
        ResponseFormatType responseFormatType = apiRequest.getFormat();
        @SuppressWarnings("unchecked")
        Map bodyLinks = (Map) responseContext.get(
                PAGINATION_LINKS_CONTEXT_KEY.getName()
        );
        if (bodyLinks == null) {
            bodyLinks = Collections.emptyMap();
        }
        Pagination pagination = (Pagination) responseContext.get(PAGINATION_CONTEXT_KEY.getName());

        // Add headers for content type
        // default response format is JSON
        if (responseFormatType == null) {
            responseFormatType = DefaultResponseFormatType.JSON;
        }


        LinkedHashMap> requestedApiDimensionFields;

        if (apiRequest instanceof DataApiRequest) {
            requestedApiDimensionFields = ((DataApiRequest) apiRequest).getDimensionFields();
        } else {
            requestedApiDimensionFields =
                    (LinkedHashMap>) responseContext.get(
                            REQUESTED_API_DIMENSION_FIELDS.getName()
                    );
        }
/*
        LinkedHashMap> requestedApiDimensionFields =
                dimensionNameToDimensionFieldMap
                        .entrySet()
                        .stream()
                        .collect(Collectors.toMap(
                                e -> dimensionDictionary.findByApiName(e.getKey()),
                                Map.Entry::getValue,
                                (value1, value2) -> value1,
                                // We won't have any collisions, so just take the first value
                                LinkedHashMap::new
                        ));
*/
        @SuppressWarnings("unchecked")
        ResponseData responseData = buildResponseData(
                resultSet,
                (LinkedHashSet) responseContext.get(API_METRIC_COLUMN_NAMES.getName()),
                requestedApiDimensionFields,
                getPartialIntervalsWithDefault(responseContext),
                getVolatileIntervalsWithDefault(responseContext),
                pagination,
                bodyLinks
        );

        StreamingOutput stream = outputStream -> {
            responseWriter.write(apiRequest, responseData, outputStream);
        };

//      pass stream handler as response
        ResponseBuilder rspBuilder = javax.ws.rs.core.Response.ok(stream);
        return buildAndAddResponseHeaders(
                rspBuilder,
                responseFormatType,
                containerRequestContext,
                apiRequest.getDownloadFilename().orElse(null)
        );
    }

    /**
     * Prepare Response object from error details with reason and description.
     *
     * @param statusCode  Error status code
     * @param reason  Brief reason about the error
     * @param description  Description of the error
     * @param druidQuery  Druid query associated with the an error
     *
     * @return Publishable Response object
     */
    public javax.ws.rs.core.Response buildErrorResponse(
            int statusCode,
            String reason,
            String description,
            DruidQuery druidQuery
    ) {
        return RequestHandlerUtils.makeErrorResponse(
                statusCode,
                reason,
                description,
                druidQuery,
                objectMappers.getMapper().writer()
        );
    }

    /**
     * Builds a ResponseData object.
     *
     * @param resultSet  ResultSet to turn into response
     * @param apiMetricColumnNames  The names of the logical metrics requested
     * @param requestedApiDimensionFields  The fields for each dimension that should be shown in the response
     * @param partialIntervals  intervals over which partial data exists
     * @param volatileIntervals  intervals over which data is understood as 'best-to-date'
     * @param pagination  The object containing the pagination information. Null if we are not paginating
     * @param paginationLinks A mapping from link names to links to be added to the end of the JSON response
     *
     * @return a new ResponseData object
     */
    protected ResponseData buildResponseData(
            ResultSet resultSet,
            LinkedHashSet apiMetricColumnNames,
            LinkedHashMap> requestedApiDimensionFields,
            SimplifiedIntervalList partialIntervals,
            SimplifiedIntervalList volatileIntervals,
            Pagination pagination,
            Map paginationLinks
    ) {
        return new ResponseData(
                resultSet,
                apiMetricColumnNames,
                requestedApiDimensionFields,
                partialIntervals,
                volatileIntervals,
                pagination,
                paginationLinks
        );
    }

    /**
     * Builds the headers for the response and gives them to the provided response builder to be added to the response.
     *
     * @param rspBuilder  ResponseBuilder that handles adding the headers to the response
     * @param responseFormatType  The type of the response
     * @param containerRequestContext  The request context
     *
     * @return the response builder that has had the headers added
     */
    protected ResponseBuilder buildAndAddResponseHeaders(
            ResponseBuilder rspBuilder,
            ResponseFormatType responseFormatType,
            ContainerRequestContext containerRequestContext
    ) {
        return buildAndAddResponseHeaders(rspBuilder, responseFormatType, containerRequestContext, null);
    }

    /**
     * Builds the headers for the response and gives them to the provided response builder to be added to the response.
     *
     * @param rspBuilder  ResponseBuilder that handles adding the headers to the response
     * @param responseFormatType  The type of the response
     * @param containerRequestContext  The request context
     * @param downloadFilename  The filename the response should be downloaded as. Null or empty indicates the response
     * should not be downloaded and instead rendered by the browser
     *
     * @return the response builder that has had the headers added
     */
    protected ResponseBuilder buildAndAddResponseHeaders(
            ResponseBuilder rspBuilder,
            ResponseFormatType responseFormatType,
            ContainerRequestContext containerRequestContext,
            String downloadFilename
    ) {
        Map responseHeaders = responseUtils.buildResponseFormatHeaders(
                containerRequestContext,
                downloadFilename,
                responseFormatType
        );

        ResponseBuilder responseBuilderWithHeaders = rspBuilder;
        for (Map.Entry entry : responseHeaders.entrySet()) {
            responseBuilderWithHeaders = responseBuilderWithHeaders.header(entry.getKey(), entry.getValue());
        }
        return responseBuilderWithHeaders;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy