Fix Smile encoding for HTTP response (#10980)

* fix Smile encoding bug

Signed-off-by: frank chen <frank.chen021@outlook.com>

* Add unit tests

* Add IT for smile encoding

* Fix cases

* Update javadoc

Co-authored-by: Jihoon Son <jihoonson@apache.org>

* resolve comments

Co-authored-by: Jihoon Son <jihoonson@apache.org>
This commit is contained in:
frank chen 2021-05-04 13:43:47 +08:00 committed by GitHub
parent 554f1ffeee
commit 204901a602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 435 additions and 104 deletions

View File

@ -21,38 +21,115 @@ package org.apache.druid.testing.clients;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
import com.google.inject.Inject;
import org.apache.druid.guice.annotations.Smile;
import org.apache.druid.java.util.common.IAE;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.http.client.HttpClient;
import org.apache.druid.java.util.http.client.Request;
import org.apache.druid.java.util.http.client.response.BytesFullResponseHandler;
import org.apache.druid.java.util.http.client.response.BytesFullResponseHolder;
import org.apache.druid.java.util.http.client.response.StatusResponseHandler;
import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
import org.apache.druid.testing.IntegrationTestingConfig;
import org.apache.druid.testing.guice.TestClient;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import javax.annotation.Nullable;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
public abstract class AbstractQueryResourceTestClient<QueryType>
{
private final ObjectMapper jsonMapper;
private final HttpClient httpClient;
final String routerUrl;
final String contentTypeHeader;
/**
* a 'null' means the Content-Type in response defaults to Content-Type of request
*/
final String acceptHeader;
final ObjectMapper jsonMapper;
final ObjectMapper smileMapper;
final HttpClient httpClient;
final String routerUrl;
final Map<String, EncoderDecoder> encoderDecoderMap;
/**
* A encoder/decoder that encodes/decodes requests/responses based on Content-Type.
*/
interface EncoderDecoder
{
byte[] encode(Object content) throws IOException;
List<Map<String, Object>> decode(byte[] content) throws IOException;
}
static class ObjectMapperEncoderDecoder implements EncoderDecoder
{
private final ObjectMapper om;
ObjectMapperEncoderDecoder(ObjectMapper om)
{
this.om = om;
}
@Override
public byte[] encode(Object content) throws IOException
{
return om.writeValueAsBytes(content);
}
@Override
public List<Map<String, Object>> decode(byte[] content) throws IOException
{
return om.readValue(content, new TypeReference<List<Map<String, Object>>>()
{
});
}
}
/**
* @param contentTypeHeader Content-Type header of HTTP request
* @param acceptHeader Accept header of HTTP request. If it's null, Content-Type in response defaults to Content-Type in request
*/
@Inject
AbstractQueryResourceTestClient(
ObjectMapper jsonMapper,
@Smile ObjectMapper smileMapper,
@TestClient HttpClient httpClient,
IntegrationTestingConfig config
String routerUrl,
String contentTypeHeader,
@Nullable String acceptHeader
)
{
this.jsonMapper = jsonMapper;
this.smileMapper = smileMapper;
this.httpClient = httpClient;
this.routerUrl = config.getRouterUrl();
this.routerUrl = routerUrl;
this.encoderDecoderMap = new HashMap<>();
this.encoderDecoderMap.put(MediaType.APPLICATION_JSON, new ObjectMapperEncoderDecoder(jsonMapper));
this.encoderDecoderMap.put(SmileMediaTypes.APPLICATION_JACKSON_SMILE, new ObjectMapperEncoderDecoder(smileMapper));
if (!this.encoderDecoderMap.containsKey(contentTypeHeader)) {
throw new IAE("Invalid Content-Type[%s]", contentTypeHeader);
}
this.contentTypeHeader = contentTypeHeader;
if (acceptHeader != null) {
if (!this.encoderDecoderMap.containsKey(acceptHeader)) {
throw new IAE("Invalid Accept[%s]", acceptHeader);
}
}
this.acceptHeader = acceptHeader;
}
public abstract String getBrokerURL();
@ -60,12 +137,18 @@ public abstract class AbstractQueryResourceTestClient<QueryType>
public List<Map<String, Object>> query(String url, QueryType query)
{
try {
StatusResponseHolder response = httpClient.go(
new Request(HttpMethod.POST, new URL(url)).setContent(
"application/json",
jsonMapper.writeValueAsBytes(query)
),
StatusResponseHandler.getInstance()
String expectedResponseType = this.contentTypeHeader;
Request request = new Request(HttpMethod.POST, new URL(url));
request.setContent(this.contentTypeHeader, encoderDecoderMap.get(this.contentTypeHeader).encode(query));
if (this.acceptHeader != null) {
expectedResponseType = this.acceptHeader;
request.addHeader(HttpHeaders.Names.ACCEPT, this.acceptHeader);
}
BytesFullResponseHolder response = httpClient.go(
request,
new BytesFullResponseHandler()
).get();
if (!response.getStatus().equals(HttpResponseStatus.OK)) {
@ -73,15 +156,20 @@ public abstract class AbstractQueryResourceTestClient<QueryType>
"Error while querying[%s] status[%s] content[%s]",
getBrokerURL(),
response.getStatus(),
response.getContent()
new String(response.getContent(), StandardCharsets.UTF_8)
);
}
return jsonMapper.readValue(
response.getContent(), new TypeReference<List<Map<String, Object>>>()
{
}
);
String responseType = response.getResponse().headers().get(HttpHeaders.Names.CONTENT_TYPE);
if (!expectedResponseType.equals(responseType)) {
throw new ISE(
"Content-Type[%s] in HTTP response does not match the expected[%s]",
responseType,
expectedResponseType
);
}
return this.encoderDecoderMap.get(responseType).decode(response.getContent());
}
catch (Exception e) {
throw new RuntimeException(e);
@ -91,11 +179,10 @@ public abstract class AbstractQueryResourceTestClient<QueryType>
public Future<StatusResponseHolder> queryAsync(String url, QueryType query)
{
try {
Request request = new Request(HttpMethod.POST, new URL(url));
request.setContent(MediaType.APPLICATION_JSON, encoderDecoderMap.get(MediaType.APPLICATION_JSON).encode(query));
return httpClient.go(
new Request(HttpMethod.POST, new URL(url)).setContent(
"application/json",
jsonMapper.writeValueAsBytes(query)
),
request,
StatusResponseHandler.getInstance()
);
}

View File

@ -22,23 +22,40 @@ package org.apache.druid.testing.clients;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import org.apache.druid.guice.annotations.Smile;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.http.client.HttpClient;
import org.apache.druid.query.Query;
import org.apache.druid.testing.IntegrationTestingConfig;
import org.apache.druid.testing.guice.TestClient;
import javax.annotation.Nullable;
import javax.ws.rs.core.MediaType;
public class QueryResourceTestClient extends AbstractQueryResourceTestClient<Query>
{
@Inject
QueryResourceTestClient(
ObjectMapper jsonMapper,
@Smile ObjectMapper smileMapper,
@TestClient HttpClient httpClient,
IntegrationTestingConfig config
)
{
super(jsonMapper, httpClient, config);
this(jsonMapper, smileMapper, httpClient, config.getRouterUrl(), MediaType.APPLICATION_JSON, null);
}
private QueryResourceTestClient(
ObjectMapper jsonMapper,
@Smile ObjectMapper smileMapper,
@TestClient HttpClient httpClient,
String routerUrl,
String contentType,
String accept
)
{
super(jsonMapper, smileMapper, httpClient, routerUrl, contentType, accept);
}
@Override
@ -50,4 +67,22 @@ public class QueryResourceTestClient extends AbstractQueryResourceTestClient<Que
);
}
/**
* clone a new instance of current object with given encoding.
* Note: For {@link AbstractQueryResourceTestClient#queryAsync(String, Object)} operation, contentType could only be application/json
*
* @param contentType Content-Type header of request. Cannot be NULL. Both application/json and application/x-jackson-smile are allowed
* @param accept Accept header of request. Both application/json and application/x-jackson-smile are allowed
*/
public QueryResourceTestClient withEncoding(String contentType, @Nullable String accept)
{
return new QueryResourceTestClient(
this.jsonMapper,
this.smileMapper,
this.httpClient,
this.routerUrl,
contentType,
accept
);
}
}

View File

@ -27,6 +27,8 @@ import org.apache.druid.sql.http.SqlQuery;
import org.apache.druid.testing.IntegrationTestingConfig;
import org.apache.druid.testing.guice.TestClient;
import javax.ws.rs.core.MediaType;
public class SqlResourceTestClient extends AbstractQueryResourceTestClient<SqlQuery>
{
@ -37,7 +39,9 @@ public class SqlResourceTestClient extends AbstractQueryResourceTestClient<SqlQu
IntegrationTestingConfig config
)
{
super(jsonMapper, httpClient, config);
// currently smile encoding is not supported on SQL endpoint
// so no need to pass smile ObjectMapper
super(jsonMapper, null, httpClient, config.getRouterUrl(), MediaType.APPLICATION_JSON, null);
}
@Override

View File

@ -43,8 +43,8 @@ public abstract class AbstractTestQueryHelper<QueryResultType extends AbstractQu
public static final Logger LOG = new Logger(TestQueryHelper.class);
private final AbstractQueryResourceTestClient queryClient;
private final ObjectMapper jsonMapper;
protected final AbstractQueryResourceTestClient queryClient;
protected final ObjectMapper jsonMapper;
protected final String broker;
protected final String brokerTLS;
protected final String router;
@ -56,13 +56,32 @@ public abstract class AbstractTestQueryHelper<QueryResultType extends AbstractQu
AbstractQueryResourceTestClient queryClient,
IntegrationTestingConfig config
)
{
this(
jsonMapper,
queryClient,
config.getBrokerUrl(),
config.getBrokerTLSUrl(),
config.getRouterUrl(),
config.getRouterTLSUrl()
);
}
AbstractTestQueryHelper(
ObjectMapper jsonMapper,
AbstractQueryResourceTestClient queryClient,
String broker,
String brokerTLS,
String router,
String routerTLS
)
{
this.jsonMapper = jsonMapper;
this.queryClient = queryClient;
this.broker = config.getBrokerUrl();
this.brokerTLS = config.getBrokerTLSUrl();
this.router = config.getRouterUrl();
this.routerTLS = config.getRouterTLSUrl();
this.broker = broker;
this.brokerTLS = brokerTLS;
this.router = router;
this.routerTLS = routerTLS;
}
public abstract String getQueryURL(String schemeAndHost);

View File

@ -25,6 +25,9 @@ import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.testing.IntegrationTestingConfig;
import org.apache.druid.testing.clients.QueryResourceTestClient;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
public class TestQueryHelper extends AbstractTestQueryHelper<QueryWithResults>
{
@ -38,10 +41,46 @@ public class TestQueryHelper extends AbstractTestQueryHelper<QueryWithResults>
super(jsonMapper, queryClient, config);
}
private TestQueryHelper(
ObjectMapper jsonMapper,
QueryResourceTestClient queryResourceTestClient,
String broker,
String brokerTLS,
String router,
String routerTLS
)
{
super(
jsonMapper,
queryResourceTestClient,
broker,
brokerTLS,
router,
routerTLS
);
}
@Override
public String getQueryURL(String schemeAndHost)
{
return StringUtils.format("%s/druid/v2?pretty", schemeAndHost);
}
/**
* clone a new instance of current object with given encoding
*
* @param contentType Content-Type header of request. Cannot be NULL. Both application/json and application/x-jackson-smile are allowed
* @param accept Accept header of request. Both application/json and application/x-jackson-smile are allowed
*/
public TestQueryHelper withEncoding(@NotNull String contentType, @Nullable String accept)
{
return new TestQueryHelper(
this.jsonMapper,
((QueryResourceTestClient) this.queryClient).withEncoding(contentType, accept),
this.broker,
this.brokerTLS,
this.router,
this.routerTLS
);
}
}

View File

@ -19,6 +19,7 @@
package org.apache.druid.tests.query;
import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import org.apache.druid.java.util.common.logger.Logger;
@ -36,9 +37,11 @@ import org.apache.druid.tests.TestNGGroup;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import javax.ws.rs.core.MediaType;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@ -79,9 +82,32 @@ public class ITWikipediaQueryTest
}
}
@Test
public void testWikipediaQueriesFromFile() throws Exception
/**
* A combination of request Content-Type and Accept HTTP header
* The first is Content-Type which can not be null while the 2nd is Accept which could be null
* <p>
* When Accept is null, its value defaults to value of Content-Type
*/
@DataProvider
public static Object[][] encodingCombination()
{
return new Object[][]{
{MediaType.APPLICATION_JSON, null},
{MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON},
{MediaType.APPLICATION_JSON, SmileMediaTypes.APPLICATION_JACKSON_SMILE},
{SmileMediaTypes.APPLICATION_JACKSON_SMILE, null},
{SmileMediaTypes.APPLICATION_JACKSON_SMILE, MediaType.APPLICATION_JSON},
{SmileMediaTypes.APPLICATION_JACKSON_SMILE, SmileMediaTypes.APPLICATION_JACKSON_SMILE},
};
}
@Test(dataProvider = "encodingCombination")
public void testWikipediaQueriesFromFile(String contentType, String accept)
throws Exception
{
// run tests on a new query helper
TestQueryHelper queryHelper = this.queryHelper.withEncoding(contentType, accept);
queryHelper.testQueriesFromFile(WIKIPEDIA_QUERIES_RESOURCE);
}

View File

@ -91,11 +91,10 @@ public class BrokerQueryResource extends QueryResource
@Context final HttpServletRequest req
) throws IOException
{
final ResourceIOReaderWriter ioReaderWriter =
createResourceIOReaderWriter(req.getContentType(), pretty != null);
final ResourceIOReaderWriter ioReaderWriter = createResourceIOReaderWriter(req, pretty != null);
try {
Query<?> query = ioReaderWriter.getInputMapper().readValue(in, Query.class);
return ioReaderWriter.ok(
Query<?> query = ioReaderWriter.getRequestMapper().readValue(in, Query.class);
return ioReaderWriter.getResponseWriter().ok(
ServerViewUtil.getTargetLocations(
brokerServerView,
query.getDataSource(),
@ -105,7 +104,7 @@ public class BrokerQueryResource extends QueryResource
);
}
catch (Exception e) {
return ioReaderWriter.gotError(e);
return ioReaderWriter.getResponseWriter().gotError(e);
}
}
}

View File

@ -183,13 +183,7 @@ public class QueryResource implements QueryCountStatsProvider
final QueryLifecycle queryLifecycle = queryLifecycleFactory.factorize();
Query<?> query = null;
String acceptHeader = req.getHeader("Accept");
if (Strings.isNullOrEmpty(acceptHeader)) {
//default to content-type
acceptHeader = req.getContentType();
}
final ResourceIOReaderWriter ioReaderWriter = createResourceIOReaderWriter(acceptHeader, pretty != null);
final ResourceIOReaderWriter ioReaderWriter = createResourceIOReaderWriter(req, pretty != null);
final String currThreadName = Thread.currentThread().getName();
try {
@ -235,7 +229,7 @@ public class QueryResource implements QueryCountStatsProvider
QueryContexts.isSerializeDateTimeAsLong(query, false)
|| (!shouldFinalize && QueryContexts.isSerializeDateTimeAsLongInner(query, false));
final ObjectWriter jsonWriter = ioReaderWriter.newOutputWriter(
final ObjectWriter jsonWriter = ioReaderWriter.getResponseWriter().newOutputWriter(
queryLifecycle.getToolChest(),
queryLifecycle.getQuery(),
serializeDateTimeAsLong
@ -276,7 +270,7 @@ public class QueryResource implements QueryCountStatsProvider
}
}
},
ioReaderWriter.getContentType()
ioReaderWriter.getResponseWriter().getResponseType()
)
.header("X-Druid-Query-Id", queryId);
@ -337,27 +331,27 @@ public class QueryResource implements QueryCountStatsProvider
catch (QueryInterruptedException e) {
interruptedQueryCount.incrementAndGet();
queryLifecycle.emitLogsAndMetrics(e, req.getRemoteAddr(), -1);
return ioReaderWriter.gotError(e);
return ioReaderWriter.getResponseWriter().gotError(e);
}
catch (QueryTimeoutException timeout) {
timedOutQueryCount.incrementAndGet();
queryLifecycle.emitLogsAndMetrics(timeout, req.getRemoteAddr(), -1);
return ioReaderWriter.gotTimeout(timeout);
return ioReaderWriter.getResponseWriter().gotTimeout(timeout);
}
catch (QueryCapacityExceededException cap) {
failedQueryCount.incrementAndGet();
queryLifecycle.emitLogsAndMetrics(cap, req.getRemoteAddr(), -1);
return ioReaderWriter.gotLimited(cap);
return ioReaderWriter.getResponseWriter().gotLimited(cap);
}
catch (QueryUnsupportedException unsupported) {
failedQueryCount.incrementAndGet();
queryLifecycle.emitLogsAndMetrics(unsupported, req.getRemoteAddr(), -1);
return ioReaderWriter.gotUnsupported(unsupported);
return ioReaderWriter.getResponseWriter().gotUnsupported(unsupported);
}
catch (BadJsonQueryException | ResourceLimitExceededException e) {
interruptedQueryCount.incrementAndGet();
queryLifecycle.emitLogsAndMetrics(e, req.getRemoteAddr(), -1);
return ioReaderWriter.gotBadQuery(e);
return ioReaderWriter.getResponseWriter().gotBadQuery(e);
}
catch (ForbiddenException e) {
// don't do anything for an authorization failure, ForbiddenExceptionMapper will catch this later and
@ -374,7 +368,7 @@ public class QueryResource implements QueryCountStatsProvider
.addData("peer", req.getRemoteAddr())
.emit();
return ioReaderWriter.gotError(e);
return ioReaderWriter.getResponseWriter().gotError(e);
}
finally {
Thread.currentThread().setName(currThreadName);
@ -389,7 +383,7 @@ public class QueryResource implements QueryCountStatsProvider
{
Query baseQuery;
try {
baseQuery = ioReaderWriter.getInputMapper().readValue(in, Query.class);
baseQuery = ioReaderWriter.getRequestMapper().readValue(in, Query.class);
}
catch (JsonParseException e) {
throw new BadJsonQueryException(e);
@ -415,47 +409,71 @@ public class QueryResource implements QueryCountStatsProvider
return mapper.copy().registerModule(new SimpleModule().addSerializer(DateTime.class, new DateTimeSerializer()));
}
protected ResourceIOReaderWriter createResourceIOReaderWriter(String requestType, boolean pretty)
protected ResourceIOReaderWriter createResourceIOReaderWriter(HttpServletRequest req, boolean pretty)
{
boolean isSmile = SmileMediaTypes.APPLICATION_JACKSON_SMILE.equals(requestType) ||
APPLICATION_SMILE.equals(requestType);
String contentType = isSmile ? SmileMediaTypes.APPLICATION_JACKSON_SMILE : MediaType.APPLICATION_JSON;
String requestType = req.getContentType();
String acceptHeader = req.getHeader("Accept");
// response type defaults to Content-Type if 'Accept' header not provided
String responseType = Strings.isNullOrEmpty(acceptHeader) ? requestType : acceptHeader;
boolean isRequestSmile = SmileMediaTypes.APPLICATION_JACKSON_SMILE.equals(requestType) || APPLICATION_SMILE.equals(requestType);
boolean isResponseSmile = SmileMediaTypes.APPLICATION_JACKSON_SMILE.equals(responseType) || APPLICATION_SMILE.equals(responseType);
return new ResourceIOReaderWriter(
contentType,
isSmile ? smileMapper : jsonMapper,
isSmile ? serializeDateTimeAsLongSmileMapper : serializeDateTimeAsLongJsonMapper,
pretty
);
isRequestSmile ? smileMapper : jsonMapper,
new ResourceIOWriter(isResponseSmile ? SmileMediaTypes.APPLICATION_JACKSON_SMILE : MediaType.APPLICATION_JSON,
isResponseSmile ? smileMapper : jsonMapper,
isResponseSmile ? serializeDateTimeAsLongSmileMapper : serializeDateTimeAsLongJsonMapper,
pretty
));
}
protected static class ResourceIOReaderWriter
{
private final String contentType;
private final ObjectMapper requestMapper;
private final ResourceIOWriter writer;
public ResourceIOReaderWriter(ObjectMapper requestMapper, ResourceIOWriter writer)
{
this.requestMapper = requestMapper;
this.writer = writer;
}
public ObjectMapper getRequestMapper()
{
return requestMapper;
}
public ResourceIOWriter getResponseWriter()
{
return writer;
}
}
protected static class ResourceIOWriter
{
private final String responseType;
private final ObjectMapper inputMapper;
private final ObjectMapper serializeDateTimeAsLongInputMapper;
private final boolean isPretty;
ResourceIOReaderWriter(
String contentType,
ResourceIOWriter(
String responseType,
ObjectMapper inputMapper,
ObjectMapper serializeDateTimeAsLongInputMapper,
boolean isPretty
)
{
this.contentType = contentType;
this.responseType = responseType;
this.inputMapper = inputMapper;
this.serializeDateTimeAsLongInputMapper = serializeDateTimeAsLongInputMapper;
this.isPretty = isPretty;
}
String getContentType()
String getResponseType()
{
return contentType;
}
ObjectMapper getInputMapper()
{
return inputMapper;
return responseType;
}
ObjectWriter newOutputWriter(
@ -476,7 +494,7 @@ public class QueryResource implements QueryCountStatsProvider
Response ok(Object object) throws IOException
{
return Response.ok(newOutputWriter(null, null, false).writeValueAsString(object), contentType).build();
return Response.ok(newOutputWriter(null, null, false).writeValueAsString(object), responseType).build();
}
Response gotError(Exception e) throws IOException
@ -510,7 +528,7 @@ public class QueryResource implements QueryCountStatsProvider
Response buildNonOkResponse(int status, Exception e) throws JsonProcessingException
{
return Response.status(status)
.type(contentType)
.type(responseType)
.entity(newOutputWriter(null, null, false).writeValueAsBytes(e))
.build();
}

View File

@ -28,6 +28,10 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Injector;
import com.google.inject.Key;
import org.apache.druid.guice.GuiceInjectors;
import org.apache.druid.guice.annotations.Smile;
import org.apache.druid.jackson.DefaultObjectMapper;
import org.apache.druid.java.util.common.concurrent.Execs;
import org.apache.druid.java.util.common.guava.LazySequence;
@ -96,7 +100,6 @@ import java.util.function.Consumer;
public class QueryResourceTest
{
private static final QueryToolChestWarehouse WAREHOUSE = new MapQueryToolChestWarehouse(ImmutableMap.of());
private static final ObjectMapper JSON_MAPPER = new DefaultObjectMapper();
private static final AuthenticationResult AUTHENTICATION_RESULT =
new AuthenticationResult("druid", "druid", null, null);
@ -178,6 +181,8 @@ public class QueryResourceTest
false
);
private ObjectMapper jsonMapper;
private ObjectMapper smileMapper;
private QueryResource queryResource;
private QueryScheduler queryScheduler;
private TestRequestLogger testRequestLogger;
@ -191,6 +196,10 @@ public class QueryResourceTest
@Before
public void setup()
{
Injector injector = GuiceInjectors.makeStartupInjector();
jsonMapper = injector.getInstance(ObjectMapper.class);
smileMapper = injector.getInstance(Key.get(ObjectMapper.class, Smile.class));
EasyMock.expect(testServletRequest.getContentType()).andReturn(MediaType.APPLICATION_JSON).anyTimes();
EasyMock.expect(testServletRequest.getHeader("Accept")).andReturn(MediaType.APPLICATION_JSON).anyTimes();
EasyMock.expect(testServletRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
@ -213,8 +222,8 @@ public class QueryResourceTest
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
Suppliers.ofInstance(new DefaultQueryConfig(ImmutableMap.of()))
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
smileMapper,
queryScheduler,
new AuthConfig(),
null,
@ -259,8 +268,8 @@ public class QueryResourceTest
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
Suppliers.ofInstance(overrideConfig)
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
smileMapper,
queryScheduler,
new AuthConfig(),
null,
@ -279,7 +288,7 @@ public class QueryResourceTest
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
((StreamingOutput) response.getEntity()).write(baos);
final List<Result<TimeBoundaryResultValue>> responses = JSON_MAPPER.readValue(
final List<Result<TimeBoundaryResultValue>> responses = jsonMapper.readValue(
baos.toByteArray(),
new TypeReference<List<Result<TimeBoundaryResultValue>>>() {}
);
@ -311,8 +320,8 @@ public class QueryResourceTest
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
Suppliers.ofInstance(overrideConfig)
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
smileMapper,
queryScheduler,
new AuthConfig(),
null,
@ -332,7 +341,7 @@ public class QueryResourceTest
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
((StreamingOutput) response.getEntity()).write(baos);
final List<Result<TimeBoundaryResultValue>> responses = JSON_MAPPER.readValue(
final List<Result<TimeBoundaryResultValue>> responses = jsonMapper.readValue(
baos.toByteArray(),
new TypeReference<List<Result<TimeBoundaryResultValue>>>() {}
);
@ -367,7 +376,7 @@ public class QueryResourceTest
).toString();
Assert.assertEquals(
expectedException,
JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryInterruptedException.class).toString()
jsonMapper.readValue((byte[]) response.getEntity(), QueryInterruptedException.class).toString()
);
}
@ -457,7 +466,7 @@ public class QueryResourceTest
}
@Test
public void testGoodQueryWithSmileAcceptHeader() throws IOException
public void testGoodQueryWithJsonRequestAndSmileAcceptHeader() throws IOException
{
//Doing a replay of testServletRequest for teardown to succeed.
//We dont use testServletRequest in this testcase
@ -466,6 +475,8 @@ public class QueryResourceTest
//Creating our own Smile Servlet request, as to not disturb the remaining tests.
// else refactoring required for this class. i know this kinda makes the class somewhat Dirty.
final HttpServletRequest smileRequest = EasyMock.createMock(HttpServletRequest.class);
// Set Content-Type to JSON
EasyMock.expect(smileRequest.getContentType()).andReturn(MediaType.APPLICATION_JSON).anyTimes();
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED))
@ -479,6 +490,7 @@ public class QueryResourceTest
smileRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
// Set Accept to Smile
EasyMock.expect(smileRequest.getHeader("Accept")).andReturn(SmileMediaTypes.APPLICATION_JACKSON_SMILE).anyTimes();
EasyMock.expect(smileRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
EasyMock.expect(smileRequest.getRemoteAddr()).andReturn("localhost").anyTimes();
@ -490,6 +502,98 @@ public class QueryResourceTest
smileRequest
);
Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
// Content-Type in response should be Smile
Assert.assertEquals(SmileMediaTypes.APPLICATION_JACKSON_SMILE, (response.getMetadata().get("Content-Type").get(0)).toString());
Assert.assertNotNull(response);
EasyMock.verify(smileRequest);
}
@Test
public void testGoodQueryWithSmileRequestAndSmileAcceptHeader() throws IOException
{
//Doing a replay of testServletRequest for teardown to succeed.
//We dont use testServletRequest in this testcase
EasyMock.replay(testServletRequest);
//Creating our own Smile Servlet request, as to not disturb the remaining tests.
// else refactoring required for this class. i know this kinda makes the class somewhat Dirty.
final HttpServletRequest smileRequest = EasyMock.createMock(HttpServletRequest.class);
// Set Content-Type to Smile
EasyMock.expect(smileRequest.getContentType()).andReturn(SmileMediaTypes.APPLICATION_JACKSON_SMILE).anyTimes();
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED))
.andReturn(null)
.anyTimes();
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
.andReturn(AUTHENTICATION_RESULT)
.anyTimes();
smileRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
// Set Accept to Smile
EasyMock.expect(smileRequest.getHeader("Accept")).andReturn(SmileMediaTypes.APPLICATION_JACKSON_SMILE).anyTimes();
EasyMock.expect(smileRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
EasyMock.expect(smileRequest.getRemoteAddr()).andReturn("localhost").anyTimes();
EasyMock.replay(smileRequest);
Response response = queryResource.doPost(
// Write input in Smile encoding
new ByteArrayInputStream(smileMapper.writeValueAsBytes(jsonMapper.readTree(SIMPLE_TIMESERIES_QUERY))),
null /*pretty*/,
smileRequest
);
Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
// Content-Type in response should be Smile
Assert.assertEquals(SmileMediaTypes.APPLICATION_JACKSON_SMILE, (response.getMetadata().get("Content-Type").get(0)).toString());
Assert.assertNotNull(response);
EasyMock.verify(smileRequest);
}
@Test
public void testGoodQueryWithSmileRequestNoSmileAcceptHeader() throws IOException
{
//Doing a replay of testServletRequest for teardown to succeed.
//We dont use testServletRequest in this testcase
EasyMock.replay(testServletRequest);
//Creating our own Smile Servlet request, as to not disturb the remaining tests.
// else refactoring required for this class. i know this kinda makes the class somewhat Dirty.
final HttpServletRequest smileRequest = EasyMock.createMock(HttpServletRequest.class);
// Set Content-Type to Smile
EasyMock.expect(smileRequest.getContentType()).andReturn(SmileMediaTypes.APPLICATION_JACKSON_SMILE).anyTimes();
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED))
.andReturn(null)
.anyTimes();
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
.andReturn(AUTHENTICATION_RESULT)
.anyTimes();
smileRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
// DO NOT set Accept to Smile, Content-Type in response will be default to Content-Type in request
EasyMock.expect(smileRequest.getHeader("Accept")).andReturn(null).anyTimes();
EasyMock.expect(smileRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
EasyMock.expect(smileRequest.getRemoteAddr()).andReturn("localhost").anyTimes();
EasyMock.replay(smileRequest);
Response response = queryResource.doPost(
// Write input in Smile encoding
new ByteArrayInputStream(smileMapper.writeValueAsBytes(jsonMapper.readTree(SIMPLE_TIMESERIES_QUERY))),
null /*pretty*/,
smileRequest
);
Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
// Content-Type in response will be default to Content-Type in request
Assert.assertEquals(SmileMediaTypes.APPLICATION_JACKSON_SMILE, (response.getMetadata().get("Content-Type").get(0)).toString());
Assert.assertNotNull(response);
EasyMock.verify(smileRequest);
@ -506,7 +610,7 @@ public class QueryResourceTest
);
Assert.assertNotNull(response);
Assert.assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
QueryException e = JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryException.class);
QueryException e = jsonMapper.readValue((byte[]) response.getEntity(), QueryException.class);
Assert.assertEquals(BadJsonQueryException.ERROR_CODE, e.getErrorCode());
Assert.assertEquals(BadJsonQueryException.ERROR_CLASS, e.getErrorClass());
}
@ -525,7 +629,7 @@ public class QueryResourceTest
);
Assert.assertNotNull(response);
Assert.assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
QueryException e = JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryException.class);
QueryException e = jsonMapper.readValue((byte[]) response.getEntity(), QueryException.class);
Assert.assertEquals(ResourceLimitExceededException.ERROR_CODE, e.getErrorCode());
Assert.assertEquals(ResourceLimitExceededException.class.getName(), e.getErrorClass());
}
@ -546,7 +650,7 @@ public class QueryResourceTest
);
Assert.assertNotNull(response);
Assert.assertEquals(QueryUnsupportedException.STATUS_CODE, response.getStatus());
QueryException ex = JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryException.class);
QueryException ex = jsonMapper.readValue((byte[]) response.getEntity(), QueryException.class);
Assert.assertEquals(errorMessage, ex.getMessage());
Assert.assertEquals(QueryUnsupportedException.ERROR_CODE, ex.getErrorCode());
}
@ -603,8 +707,8 @@ public class QueryResourceTest
authMapper,
Suppliers.ofInstance(new DefaultQueryConfig(ImmutableMap.of()))
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
smileMapper,
queryScheduler,
new AuthConfig(),
authMapper,
@ -632,7 +736,7 @@ public class QueryResourceTest
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
((StreamingOutput) response.getEntity()).write(baos);
final List<Result<TimeBoundaryResultValue>> responses = JSON_MAPPER.readValue(
final List<Result<TimeBoundaryResultValue>> responses = jsonMapper.readValue(
baos.toByteArray(),
new TypeReference<List<Result<TimeBoundaryResultValue>>>() {}
);
@ -679,8 +783,8 @@ public class QueryResourceTest
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
Suppliers.ofInstance(new DefaultQueryConfig(ImmutableMap.of()))
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
jsonMapper,
queryScheduler,
new AuthConfig(),
null,
@ -697,7 +801,7 @@ public class QueryResourceTest
Assert.assertEquals(QueryTimeoutException.STATUS_CODE, response.getStatus());
QueryTimeoutException ex;
try {
ex = JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryTimeoutException.class);
ex = jsonMapper.readValue((byte[]) response.getEntity(), QueryTimeoutException.class);
}
catch (IOException e) {
throw new RuntimeException(e);
@ -777,8 +881,8 @@ public class QueryResourceTest
authMapper,
Suppliers.ofInstance(new DefaultQueryConfig(ImmutableMap.of()))
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
smileMapper,
queryScheduler,
new AuthConfig(),
authMapper,
@ -901,8 +1005,8 @@ public class QueryResourceTest
authMapper,
Suppliers.ofInstance(new DefaultQueryConfig(ImmutableMap.of()))
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
smileMapper,
queryScheduler,
new AuthConfig(),
authMapper,
@ -995,7 +1099,7 @@ public class QueryResourceTest
Assert.assertEquals(QueryCapacityExceededException.STATUS_CODE, response.getStatus());
QueryCapacityExceededException ex;
try {
ex = JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryCapacityExceededException.class);
ex = jsonMapper.readValue((byte[]) response.getEntity(), QueryCapacityExceededException.class);
}
catch (IOException e) {
throw new RuntimeException(e);
@ -1036,7 +1140,7 @@ public class QueryResourceTest
Assert.assertEquals(QueryCapacityExceededException.STATUS_CODE, response.getStatus());
QueryCapacityExceededException ex;
try {
ex = JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryCapacityExceededException.class);
ex = jsonMapper.readValue((byte[]) response.getEntity(), QueryCapacityExceededException.class);
}
catch (IOException e) {
throw new RuntimeException(e);
@ -1088,7 +1192,7 @@ public class QueryResourceTest
Assert.assertEquals(QueryCapacityExceededException.STATUS_CODE, response.getStatus());
QueryCapacityExceededException ex;
try {
ex = JSON_MAPPER.readValue((byte[]) response.getEntity(), QueryCapacityExceededException.class);
ex = jsonMapper.readValue((byte[]) response.getEntity(), QueryCapacityExceededException.class);
}
catch (IOException e) {
throw new RuntimeException(e);
@ -1160,8 +1264,8 @@ public class QueryResourceTest
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
Suppliers.ofInstance(new DefaultQueryConfig(ImmutableMap.of()))
),
JSON_MAPPER,
JSON_MAPPER,
jsonMapper,
smileMapper,
scheduler,
new AuthConfig(),
null,