Skip to content

Commit

Permalink
Allow header to control report format
Browse files Browse the repository at this point in the history
  • Loading branch information
Tianyuan Zhang authored and michael-mclawhorn committed Oct 9, 2017
1 parent 8ee9972 commit bdb3d60
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 17 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ Current

### Added:

- [Evaluate format type from both URI and Accept header](https://github.com/yahoo/fili/pull/495)
* Add a new functional interface `ResponseFormatResolver` to coalesce Accept header format type and URI format type.
* Implement a concrete implementation of `ResponseFormatResolver` in `AbstractBindingFactory`.

- [Add Constructor and wither for TableApiRequest](https://github.com/yahoo/fili/pull/539)
* Making the TablesApiRequest similar to other ApiRequest classses so added an all argument constructor
and withers. The all argument constructor is made private since its used only by the withers.

- [Add Code Narc to validate Groovy style](https://github.com/yahoo/fili/pull/420)
* Checkstyle is great, but it doesn't process Groovy. Code Narc is Checkstyle for Groovy, so we should totally use
it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import com.yahoo.bard.webservice.util.SimplifiedIntervalList;
import com.yahoo.bard.webservice.web.CsvResponseWriter;
import com.yahoo.bard.webservice.web.DataApiRequest;
import com.yahoo.bard.webservice.web.DefaultResponseFormatResolver;
import com.yahoo.bard.webservice.web.DimensionApiRequestMapper;
import com.yahoo.bard.webservice.web.DimensionsApiRequest;
import com.yahoo.bard.webservice.web.FiliResponseWriter;
Expand All @@ -94,6 +95,7 @@
import com.yahoo.bard.webservice.web.MetricsFilterSetBuilder;
import com.yahoo.bard.webservice.web.NoOpRequestMapper;
import com.yahoo.bard.webservice.web.RequestMapper;
import com.yahoo.bard.webservice.web.ResponseFormatResolver;
import com.yahoo.bard.webservice.web.ResponseWriter;
import com.yahoo.bard.webservice.web.SlicesApiRequest;
import com.yahoo.bard.webservice.web.TablesApiRequest;
Expand Down Expand Up @@ -317,6 +319,8 @@ protected void configure() {

bind(buildResponseWriter(getMappers())).to(ResponseWriter.class);

bind(buildResponseFormatResolver()).to(ResponseFormatResolver.class);

if (DRUID_DIMENSIONS_LOADER.isOn()) {
DimensionValueLoadTask dimensionLoader = buildDruidDimensionsLoader(
druidWebService,
Expand Down Expand Up @@ -1123,6 +1127,17 @@ protected DruidWebService buildMetadataDruidWebService(ObjectMapper mapper) {
return buildDruidWebService(DruidClientConfigHelper.getMetadataServiceConfig(), mapper);
}

/**
* Create a ResponseFormatResolver for Servlet objects.
* <p>
* Currently default types are json, jsonapi and csv types.
*
* @return A ResponseFormatResolver
*/
protected ResponseFormatResolver buildResponseFormatResolver() {
return new DefaultResponseFormatResolver();
}

@Override
public void afterRegistration(ResourceConfig resourceConfig) {
// NoOp by default
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2017 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 java.util.LinkedHashMap;
import java.util.Map;

import javax.ws.rs.container.ContainerRequestContext;

/**
* A Fili default implementation of ResponseFormatResolver. This implementation works with three formats: json, jsonapi
* and csv.
*/
public class DefaultResponseFormatResolver implements ResponseFormatResolver {
public static final String ACCEPT_HEADER_JSON = "application/json";
public static final String ACCEPT_HEADER_JSONAPI = "application/vnd.api+json";
public static final String ACCEPT_HEADER_CSV = "text/csv";
public static final String URI_JSON = "json";
public static final String URI_JSONAPI = "jsonapi";
public static final String URI_CSV = "csv";

private final Map<String, String> formatsMap;

/**
* Constructor.
*/
public DefaultResponseFormatResolver() {
formatsMap = new LinkedHashMap<>();
formatsMap.put(ACCEPT_HEADER_JSON, URI_JSON);
formatsMap.put(ACCEPT_HEADER_JSONAPI, URI_JSONAPI);
formatsMap.put(ACCEPT_HEADER_CSV, URI_CSV);
}

@Override
public String apply(final String format, final ContainerRequestContext containerRequestContext) {
String headerFormat = containerRequestContext.getHeaderString("Accept");
if (format != null || headerFormat == null) {
return format;
}
return formatsMap.entrySet().stream()
.filter(entry -> headerFormat.contains(entry.getKey()))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2017 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 java.util.function.BiFunction;

import javax.ws.rs.container.ContainerRequestContext;

/**
* A functional interface which allows the logic of resolving response format customizable. It has one method that
* takes the format name string from URI and a ContainerRequestContext object from which the header Accept field is
* accessible.
*/
public interface ResponseFormatResolver extends BiFunction<String, ContainerRequestContext, String> {
/**
* Resolve desirable format from URI and ContainerRequestContext.
*
* @param format The format String from URI
* @param containerRequestContext ContainerRequestContext object that contains request related information
*
* @return A resolved format decided by the function, null if none match
*/
String apply(String format, ContainerRequestContext containerRequestContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import com.yahoo.bard.webservice.web.RequestValidationException;
import com.yahoo.bard.webservice.web.apirequest.DataApiRequestImpl;
import com.yahoo.bard.webservice.web.apirequest.HavingGenerator;
import com.yahoo.bard.webservice.web.ResponseFormatResolver;
import com.yahoo.bard.webservice.web.handlers.DataRequestHandler;
import com.yahoo.bard.webservice.web.handlers.RequestContext;
import com.yahoo.bard.webservice.web.handlers.RequestHandlerUtils;
Expand Down Expand Up @@ -120,6 +121,7 @@ public class DataServlet extends CORSPreflightServlet implements BardConfigResou
private final ObjectWriter writer;
private final ObjectMappersSuite objectMappers;
private final HttpResponseMaker httpResponseMaker;
private final ResponseFormatResolver formatResolver;

// Default JodaTime zone to UTC
private final DateTimeZone systemTimeZone = DateTimeZone.forID(SYSTEM_CONFIG.getStringProperty(
Expand All @@ -146,6 +148,7 @@ public class DataServlet extends CORSPreflightServlet implements BardConfigResou
* @param preResponseStoredNotifications The broadcast channel responsible for notifying other Bard processes
* @param httpResponseMaker The factory for building HTTP responses
* that a query has been completed and its results stored in the
* @param formatResolver The formatResolver for determining correct response format
* {@link com.yahoo.bard.webservice.async.preresponses.stores.PreResponseStore}
*/
@Inject
Expand All @@ -164,7 +167,8 @@ public DataServlet(
JobRowBuilder jobRowBuilder,
AsynchronousWorkflowsBuilder asynchronousWorkflowsBuilder,
BroadcastChannel<String> preResponseStoredNotifications,
HttpResponseMaker httpResponseMaker
HttpResponseMaker httpResponseMaker,
ResponseFormatResolver formatResolver
) {
this.resourceDictionaries = resourceDictionaries;
this.druidQueryBuilder = druidQueryBuilder;
Expand All @@ -182,6 +186,7 @@ public DataServlet(
this.asynchronousWorkflowsBuilder = asynchronousWorkflowsBuilder;
this.preResponseStoredNotifications = preResponseStoredNotifications;
this.httpResponseMaker = httpResponseMaker;
this.formatResolver = formatResolver;

LOG.trace(
"Initialized with ResourceDictionaries: {} \n\n" +
Expand Down Expand Up @@ -371,7 +376,7 @@ public void getData(
sorts,
count,
topN,
format,
formatResolver.apply(format, containerRequestContext),
timeZone,
asyncAfter,
perPage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.yahoo.bard.webservice.web.DimensionsApiRequest;
import com.yahoo.bard.webservice.web.RequestMapper;
import com.yahoo.bard.webservice.web.RequestValidationException;
import com.yahoo.bard.webservice.web.ResponseFormatResolver;
import com.yahoo.bard.webservice.web.RowLimitReachedException;
import com.yahoo.bard.webservice.web.util.PaginationParameters;

Expand Down Expand Up @@ -67,6 +68,7 @@ public class DimensionsServlet extends EndpointServlet {
private final DimensionDictionary dimensionDictionary;
private final LogicalTableDictionary logicalTableDictionary;
private final RequestMapper requestMapper;
private final ResponseFormatResolver formatResolver;

/**
* Constructor.
Expand All @@ -75,18 +77,21 @@ public class DimensionsServlet extends EndpointServlet {
* @param logicalTableDictionary All logical tables
* @param requestMapper Mapper to change the API request if needed
* @param objectMappers JSON tools
* @param formatResolver The formatResolver for determining correct response format
*/
@Inject
public DimensionsServlet(
DimensionDictionary dimensionDictionary,
LogicalTableDictionary logicalTableDictionary,
@Named(DimensionsApiRequest.REQUEST_MAPPER_NAMESPACE) RequestMapper requestMapper,
ObjectMappersSuite objectMappers
ObjectMappersSuite objectMappers,
ResponseFormatResolver formatResolver
) {
super(objectMappers);
this.dimensionDictionary = dimensionDictionary;
this.logicalTableDictionary = logicalTableDictionary;
this.requestMapper = requestMapper;
this.formatResolver = formatResolver;
}

/**
Expand Down Expand Up @@ -123,7 +128,7 @@ public Response getAllDimensions(
DimensionsApiRequest apiRequest = new DimensionsApiRequest(
null,
null,
format,
formatResolver.apply(format, containerRequestContext),
perPage,
page,
dimensionDictionary,
Expand Down Expand Up @@ -275,7 +280,7 @@ public Response getDimensionRows(
DimensionsApiRequest apiRequest = new DimensionsApiRequest(
dimensionName,
filterQuery,
format,
formatResolver.apply(format, containerRequestContext),
perPage,
page,
dimensionDictionary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.yahoo.bard.webservice.web.PreResponse;
import com.yahoo.bard.webservice.web.RequestMapper;
import com.yahoo.bard.webservice.web.RequestValidationException;
import com.yahoo.bard.webservice.web.ResponseFormatResolver;
import com.yahoo.bard.webservice.web.ResponseFormatType;
import com.yahoo.bard.webservice.web.handlers.RequestHandlerUtils;
import com.yahoo.bard.webservice.web.responseprocessors.ResponseContext;
Expand Down Expand Up @@ -91,6 +92,7 @@ public class JobsServlet extends EndpointServlet {
private final BroadcastChannel<String> broadcastChannel;
private final ObjectWriter writer;
private final HttpResponseMaker httpResponseMaker;
private final ResponseFormatResolver formatResolver;

/**
* Constructor.
Expand All @@ -101,6 +103,7 @@ public class JobsServlet extends EndpointServlet {
* @param broadcastChannel Channel to notify other Bard processes (i.e. long pollers)
* @param requestMapper Mapper for changing the API request
* @param httpResponseMaker The factory for building HTTP responses
* @param formatResolver The formatResolver for determining correct response format
*/
@Inject
public JobsServlet(
Expand All @@ -110,7 +113,8 @@ public JobsServlet(
PreResponseStore preResponseStore,
BroadcastChannel<String> broadcastChannel,
@Named(JobsApiRequest.REQUEST_MAPPER_NAMESPACE) RequestMapper requestMapper,
HttpResponseMaker httpResponseMaker
HttpResponseMaker httpResponseMaker,
ResponseFormatResolver formatResolver
) {
super(objectMappers);
this.requestMapper = requestMapper;
Expand All @@ -120,6 +124,7 @@ public JobsServlet(
this.broadcastChannel = broadcastChannel;
this.writer = objectMappers.getMapper().writer();
this.httpResponseMaker = httpResponseMaker;
this.formatResolver = formatResolver;
}

/**
Expand Down Expand Up @@ -152,7 +157,7 @@ public void getJobs(
RequestLog.record(new JobRequest("all"));

JobsApiRequest apiRequest = new JobsApiRequest(
format,
formatResolver.apply(format, containerRequestContext),
null, //asyncAfter is null so it behaves like a synchronous request
perPage,
page,
Expand Down Expand Up @@ -280,7 +285,7 @@ public void getJobResultsByTicket(
RequestLog.record(new JobRequest(ticket));

JobsApiRequest apiRequest = new JobsApiRequest(
format,
formatResolver.apply(format, containerRequestContext),
asyncAfter,
perPage,
page,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.yahoo.bard.webservice.web.MetricsApiRequest;
import com.yahoo.bard.webservice.web.RequestMapper;
import com.yahoo.bard.webservice.web.RequestValidationException;
import com.yahoo.bard.webservice.web.ResponseFormatResolver;

import com.codahale.metrics.annotation.Timed;

Expand Down Expand Up @@ -58,6 +59,7 @@ public class MetricsServlet extends EndpointServlet {
private final MetricDictionary metricDictionary;
private final LogicalTableDictionary logicalTableDictionary;
private final RequestMapper requestMapper;
private final ResponseFormatResolver formatResolver;

/**
* Constructor.
Expand All @@ -66,18 +68,21 @@ public class MetricsServlet extends EndpointServlet {
* @param logicalTableDictionary Logical tables to know about
* @param requestMapper Mapper to change the API request if needed
* @param objectMappers JSON tools
* @param formatResolver The formatResolver for determining correct response format
*/
@Inject
public MetricsServlet(
MetricDictionary metricDictionary,
LogicalTableDictionary logicalTableDictionary,
@Named(MetricsApiRequest.REQUEST_MAPPER_NAMESPACE) RequestMapper requestMapper,
ObjectMappersSuite objectMappers
ObjectMappersSuite objectMappers,
ResponseFormatResolver formatResolver
) {
super(objectMappers);
this.metricDictionary = metricDictionary;
this.logicalTableDictionary = logicalTableDictionary;
this.requestMapper = requestMapper;
this.formatResolver = formatResolver;
}

/**
Expand Down Expand Up @@ -114,7 +119,7 @@ public Response getAllLogicalMetrics(

MetricsApiRequest apiRequest = new MetricsApiRequest(
null,
format,
formatResolver.apply(format, containerRequestContext),
perPage,
page,
metricDictionary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.yahoo.bard.webservice.table.PhysicalTableDictionary;
import com.yahoo.bard.webservice.web.RequestMapper;
import com.yahoo.bard.webservice.web.RequestValidationException;
import com.yahoo.bard.webservice.web.ResponseFormatResolver;
import com.yahoo.bard.webservice.web.SlicesApiRequest;

import com.codahale.metrics.annotation.Timed;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class SlicesServlet extends EndpointServlet {
private final DataSourceMetadataService dataSourceMetadataService;
private final PhysicalTableDictionary physicalTableDictionary;
private final RequestMapper requestMapper;
private final ResponseFormatResolver formatResolver;

/**
* Constructor.
Expand All @@ -61,18 +63,21 @@ public class SlicesServlet extends EndpointServlet {
* @param requestMapper Mapper for changing the API request
* @param dataSourceMetadataService The data source metadata provider
* @param objectMappers JSON tools
* @param formatResolver The formatResolver for determining correct response format
*/
@Inject
public SlicesServlet(
PhysicalTableDictionary physicalTableDictionary,
@Named(SlicesApiRequest.REQUEST_MAPPER_NAMESPACE) RequestMapper requestMapper,
DataSourceMetadataService dataSourceMetadataService,
ObjectMappersSuite objectMappers
ObjectMappersSuite objectMappers,
ResponseFormatResolver formatResolver
) {
super(objectMappers);
this.physicalTableDictionary = physicalTableDictionary;
this.requestMapper = requestMapper;
this.dataSourceMetadataService = dataSourceMetadataService;
this.formatResolver = formatResolver;
}

/**
Expand Down Expand Up @@ -112,7 +117,7 @@ public Response getSlices(

SlicesApiRequest apiRequest = new SlicesApiRequest(
null,
format,
formatResolver.apply(format, containerRequestContext),
perPage,
page,
physicalTableDictionary,
Expand Down
Loading

0 comments on commit bdb3d60

Please sign in to comment.