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

com.yahoo.bard.webservice.web.AbstractResponse 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.web;

import com.yahoo.bard.webservice.application.ObjectMappersSuite;
import com.yahoo.bard.webservice.util.Pagination;
import com.yahoo.bard.webservice.web.responseprocessors.MappingResponseProcessor;
import com.yahoo.bard.webservice.web.responseprocessors.ResponseContext;
import com.yahoo.bard.webservice.web.responseprocessors.ResponseContextKeys;
import com.yahoo.bard.webservice.web.util.PaginationLink;

import com.fasterxml.jackson.core.JsonGenerator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

/**
 * Formats data of a response as JSON.
 *
 * @param  the type of the raw data
 */
public abstract class AbstractResponse implements ResponseStream {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractResponse.class);
    private static final String PAGE = "page";
    private static final String PER_PAGE = "perPage";

    protected final Stream entries;
    protected final UriInfo uriInfo;
    protected final Pagination pages;
    protected final ObjectMappersSuite objectMappers;

    /**
     * Constructor.
     *
     * @param entries  The data entries to generate the response for
     * @param uriInfo  UriInfo to generate the URL for the page links
     * @param pages  The paginated set of results containing the pages being linked to.
     * @param objectMappers  Suite of Object Mappers to use when serializing the response
     */
    public AbstractResponse(Stream entries, Pagination pages, UriInfo uriInfo, ObjectMappersSuite objectMappers) {
        this.entries = entries;
        this.pages = pages;
        this.uriInfo = uriInfo;
        this.objectMappers = objectMappers;
    }

    /**
     * Get a resource method that can be used to stream this response as an entity.
     *
     * @return The resource method
     */
    @Override
    public StreamingOutput getResponseStream() {
        return this::write;
    }

    /**
     * Write the response to the output stream.
     *
     * @param outputStream  Output stream to write to
     * @throws IOException if there's a problem writing to the stream
     */
    public abstract void write(OutputStream outputStream) throws IOException;

    /**
     * Returns all required pagination links.
     *
     * @param pages  The paginated set of results containing the pages being linked to.
     * @param uriBuilder  The builder for creating the pagination links.
     *
     * @return the map of metadata for this response
     */
    public static Map addLinks(Pagination pages, UriBuilder uriBuilder) {
        Map bodyLinks = new LinkedHashMap<>();

        Arrays.stream(PaginationLink.values()).forEachOrdered(link -> addLink(link, pages, uriBuilder, bodyLinks));

        Map metaBlock = new LinkedHashMap<>();
        if (!bodyLinks.isEmpty()) {
            metaBlock.put(ResponseContextKeys.PAGINATION_LINKS_CONTEXT_KEY.getName(), bodyLinks);
        }

        return metaBlock;
    }

    /**
     * Adds the specified link to the headers and body of the response.
     *
     * @param link  The type of the link being added
     * @param pages  The paginated set of results containing the pages being linked to.
     * @param uriBuilder  The builder for creating the pagination links.
     * @param bodyLinks  The map of links that will be added to the body of the response
     */
    public static void addLink(
            PaginationLink link,
            Pagination pages,
            UriBuilder uriBuilder,
            Map bodyLinks
    ) {
        link.getPage(pages).ifPresent(page -> addLink(link, page, pages.getPerPage(), uriBuilder, bodyLinks));
    }

    /**
     * Adds the specified link to the map of links that will be added to the body of the response.
     *
     * @param paginationLink  The type of the link being added
     * @param pageNumber  The page being linked to
     * @param perPage  The number of result rows in the page being linked to
     * @param uriBuilder  The builder for creating the pagination links
     * @param bodyLinks  The map of links that will be added to the body of the response
     */
    private static void addLink(
            PaginationLink paginationLink,
            int pageNumber,
            int perPage,
            UriBuilder uriBuilder,
            Map bodyLinks
    ) {
        bodyLinks.put(
                paginationLink.getBodyName(),
                uriBuilder
                        .replaceQueryParam(PAGE, pageNumber)
                        .replaceQueryParam(PER_PAGE, perPage)
                        .build()
        );
    }

    /**
     * Adds all the required pagination links to the headers and body of the response.
     *
     * @param pages  The paginated set of results containing the pages being linked to.
     * @param uriBuilder  The uri builder to build the links
     * @param responseProcessor  The response processor whose links are being built
     */
    public static void addLinks(
            Pagination pages,
            UriBuilder uriBuilder,
            MappingResponseProcessor responseProcessor
    ) {
        LinkedHashMap bodyLinks = new LinkedHashMap<>();

        Arrays.stream(PaginationLink.values())
                .forEachOrdered(link -> addLink(link, pages, uriBuilder, responseProcessor, bodyLinks));

        ResponseContext responseContext = responseProcessor.getResponseContext();
        responseContext.put(ResponseContextKeys.PAGINATION_LINKS_CONTEXT_KEY.getName(), bodyLinks);
        responseContext.put(ResponseContextKeys.PAGINATION_CONTEXT_KEY.getName(), pages);
    }

    /**
     * Adds the specified link to the headers and body of the response.
     *
     * @param link  The type of the link being added
     * @param pages  The paginated set of results containing the pages being linked to.
     * @param uriBuilder  The uri builder to build the links
     * @param responseProcessor  The response processor whose links are being built
     * @param bodyLinks  The map of links that will be added to the body of the response
     */
    public static void addLink(
            PaginationLink link,
            Pagination pages,
            UriBuilder uriBuilder,
            MappingResponseProcessor responseProcessor,
            Map bodyLinks
    ) {
        link.getPage(pages).ifPresent(
                page -> addLink(link, page, pages.getPerPage(), uriBuilder, responseProcessor, bodyLinks)
        );
    }

    /**
     * Adds the specified link to the headers and to the map of links that will be added to the body of the response.
     *
     * @param link  The type of the link being added
     * @param pageNumber  The page being linked to
     * @param perPage  The number of result rows in the page being linked to
     * @param uriBuilder  The uri builder to build the links
     * @param responseProcessor  The response processor whose links are being built
     * @param bodyLinks  The map of links that will be added to the body of the response
     */
    public static void addLink(
            PaginationLink link,
            int pageNumber,
            int perPage,
            UriBuilder uriBuilder,
            MappingResponseProcessor responseProcessor,
            Map bodyLinks
    ) {
        UriBuilder pageLink = uriBuilder.replaceQueryParam(PAGE, pageNumber).replaceQueryParam(PER_PAGE, perPage);
        responseProcessor.getHeaders().add(
                HttpHeaders.LINK,
                Link.fromUriBuilder(pageLink).rel(link.getHeaderName()).build().toString()
        );
        bodyLinks.put(link.getBodyName(), pageLink.build());
    }

    /**
     * Builds the meta data entry of the page if such data is present.
     * Currently it contains only the pagination related metadata.
     *
     * @param generator  The JsonGenerator used to build the JSON response.
     * @param uriBuilder  The builder for creating the pagination links.
     *
     * @throws IOException if the generator throws an IOException.
     */
    public void writeMetaObject(JsonGenerator generator, UriBuilder uriBuilder) throws IOException {
        Map meta = pages == null ? Collections.emptyMap() : addLinks(pages, uriBuilder);

        if (meta.isEmpty()) {
            return;
        }

        generator.writeObjectFieldStart("meta");
        generator.writeObjectFieldStart("pagination");

        try {
            meta.forEach(
                    (k, v) -> {
                        try {
                            generator.writeObjectField(k, v);
                        } catch (IOException ioe) {
                            String msg = String.format("Unable to write pagination field with key %s", k);
                            LOG.error(msg, ioe);
                            throw new UncheckedIOException(msg, ioe);
                        }
                    }
            );
        } catch (UncheckedIOException re) {
            throw new IOException(re.getMessage(), re);
        }

        generator.writeNumberField("currentPage", pages.getPage());
        generator.writeNumberField("rowsPerPage", pages.getPerPage());
        generator.writeNumberField("numberOfResults", pages.getNumResults());
        generator.writeEndObject();
        generator.writeEndObject();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy