Skip to content

Commit

Permalink
Merge 250403b into f0c5ff0
Browse files Browse the repository at this point in the history
  • Loading branch information
yrkkap committed Oct 11, 2019
2 parents f0c5ff0 + 250403b commit 6ba7bc3
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 98 deletions.
33 changes: 33 additions & 0 deletions src/main/java/coresearch/cvurl/io/mapper/BodyType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package coresearch.cvurl.io.mapper;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
* Class that describes type with generics. Used as parameter to some of {@link coresearch.cvurl.io.request.Request}
* methods to provide ability to parse response to body to class with generics.
* Usage example: <br/>
* {@code cvurl.get(url).asObject(new BodyType<List<Users>() {});} <br/>
* this code snippet would parse response body to {@code List<User>}
*/
public abstract class BodyType<T> {

private static final String ERROR_MESSAGE = "Type should be direct child type of BodyType";

private Type type;

protected BodyType() {
ParameterizedType superclass = (ParameterizedType) this.getClass().getGenericSuperclass();
if (superclass.getRawType() != BodyType.class) {
throw new IllegalStateException(ERROR_MESSAGE);
}
this.type = superclass.getActualTypeArguments()[0];
}

/**
* @return actual type
*/
public Type getType() {
return type;
}
}
19 changes: 19 additions & 0 deletions src/main/java/coresearch/cvurl/io/mapper/GenericMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ public final <T> T readResponseBody(Response<String> response, Class<T> type) {
}
}

public final <T> T readResponseBody(Response<String> response, BodyType<T> type) {
try {
return readValue(response.getBody(), type);
} catch (MappingException e) {
throw new ResponseMappingException(e.getMessage(), e, response);
}
}

/**
* Deserialize String value to object of specified type.
*
Expand All @@ -29,6 +37,17 @@ public final <T> T readResponseBody(Response<String> response, Class<T> type) {
*/
public abstract <T> T readValue(String value, Class<T> valueType);

/**
* Deserialize String value to object of specified type.Should be used when you need to deserialize
* to type with generics.
*
* @param value value to be converted.
* @param valueType type to object of which value should be converted.
* @param <T> concrete type
* @return converted object
*/
public abstract <T> T readValue(String value, BodyType<T> valueType);

/**
* Serialize object to String.
*
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/coresearch/cvurl/io/mapper/impl/JacksonMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import coresearch.cvurl.io.exception.MappingException;
import coresearch.cvurl.io.mapper.BodyType;
import coresearch.cvurl.io.mapper.GenericMapper;

import java.io.IOException;
Expand All @@ -24,6 +25,16 @@ public <T> T readValue(String value, Class<T> valueType) {
}
}

@Override
public <T> T readValue(String value, BodyType<T> valueType) {
try {
return this.objectMapper.readValue(value,
this.objectMapper.getTypeFactory().constructType(valueType.getType()));
} catch (IOException e) {
throw new MappingException(e.getMessage(), e);
}
}

@Override
public String writeValue(Object value) {
try {
Expand Down
134 changes: 39 additions & 95 deletions src/main/java/coresearch/cvurl/io/request/CVurlRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import coresearch.cvurl.io.exception.RequestExecutionException;
import coresearch.cvurl.io.exception.UnexpectedResponseException;
import coresearch.cvurl.io.mapper.BodyType;
import coresearch.cvurl.io.mapper.GenericMapper;
import coresearch.cvurl.io.model.Response;
import coresearch.cvurl.io.request.handler.CompressedInputStreamBodyHandler;
Expand Down Expand Up @@ -39,125 +40,72 @@ public final class CVurlRequest implements Request {
this.acceptCompressed = acceptCompressed;
}

/**
* Sends current request asynchronously. If response status code
* matches provided status code then returns {@link CompletableFuture}
* with object of provided type. Otherwise returns {@link CompletableFuture}
* that finishes exceptionally with {@link UnexpectedResponseException}.
*
* @param type type of object to convert response body.
* @param statusCode status code on which converting should be done
* @param <T> type of object to convert response body
* @return {@link CompletableFuture} with object of provided type or {@link CompletableFuture}
* that finishes exceptionally with {@link UnexpectedResponseException}
*/
@Override
public <T> CompletableFuture<T> asyncAsObject(Class<T> type, int statusCode) {
return this.httpClient.sendAsync(httpRequest, getStringBodyHandler())
.thenApply((response -> parseResponse(response, type, statusCode)));
}

/**
* Sends current request asynchronously.
*
* @param type type of object to convert response body.
* @param <T> type of object to convert response body
* @return {@link CompletableFuture} with object of provided type or {@link CompletableFuture}
* that finishes exceptionally with {@link coresearch.cvurl.io.exception.ResponseMappingException} or
* {@link RequestExecutionException}
*/
@Override
public <T> CompletableFuture<T> asyncAsObject(BodyType<T> type, int statusCode) {
return this.httpClient.sendAsync(httpRequest, getStringBodyHandler())
.thenApply((response -> parseResponse(response, type, statusCode)));
}

@Override
public <T> CompletableFuture<T> asyncAsObject(Class<T> type) {
return this.httpClient.sendAsync(httpRequest, getStringBodyHandler())
.thenApply((response -> genericMapper.readResponseBody(new Response<>(response), type)));
}

/**
* Sends current request asynchronously.
*
* @return {@link CompletableFuture} with returned response.
*/
@Override
public <T> CompletableFuture<T> asyncAsObject(BodyType<T> type) {
return this.httpClient.sendAsync(httpRequest, getStringBodyHandler())
.thenApply((response -> genericMapper.readResponseBody(new Response<>(response), type)));
}

@Override
public CompletableFuture<Response<String>> asyncAsString() {
return this.httpClient.sendAsync(httpRequest, getStringBodyHandler()).thenApply(Response::new);
}

/**
* Sends current request asynchronously.
*
* @return {@link CompletableFuture} with returned response.
*/
@Override
public CompletableFuture<Response<String>> asyncAsString(HttpResponse.PushPromiseHandler<String> pph) {
return this.httpClient.sendAsync(httpRequest, getStringBodyHandler(), pph).thenApply(Response::new);
}

/**
* Sends current request asynchronously. Returns response with body as {@link InputStream}
*
* @return {@link CompletableFuture} with returned response.
*/
@Override
public CompletableFuture<Response<InputStream>> asyncAsStream() {
return this.httpClient.sendAsync(httpRequest, getStreamBodyHandler()).thenApply(Response::new);
}

/**
* Sends current request asynchronously. Returns response with body as {@link InputStream}
*
* @return {@link CompletableFuture} with returned response.
*/
@Override
public CompletableFuture<Response<InputStream>> asyncAsStream(HttpResponse.PushPromiseHandler<InputStream> pph) {
return this.httpClient.sendAsync(httpRequest, getStreamBodyHandler(), pph).thenApply(Response::new);
}

/**
* Sends current request asynchronously. Applies provided bodyHandler to the response body.
*
* @return {@link CompletableFuture} with returned response.
*/
@Override
public <T> CompletableFuture<Response<T>> asyncAs(HttpResponse.BodyHandler<T> bodyHandler) {
return this.httpClient.sendAsync(httpRequest, bodyHandler).thenApply(Response::new);
}

/**
* Sends current request asynchronously. Applies provided bodyHandler to the response body.
*
* @return {@link CompletableFuture} with returned response.
*/
@Override
public <T> CompletableFuture<Response<T>> asyncAs(HttpResponse.BodyHandler<T> bodyHandler, HttpResponse.PushPromiseHandler<T> pph) {
return this.httpClient.sendAsync(httpRequest, bodyHandler, pph).thenApply(Response::new);
}

/**
* Sends current request blocking if necessary to get
* the response. Converts response body to specified type if
* provided statusCode matches response status code and returns empty optional otherwise.
* Is some error happens during request sending or response body conversion returns empty optional.
*
* @param type type of object to convert response body.
* @param statusCode status code on which converting should be done
* @param <T> type of object to convert response body
* @return object of specified type
*/
@Override
public <T> Optional<T> asObject(Class<T> type, int statusCode) {
return sendRequestAndWrapInOptional(getStringBodyHandler(),
(response) -> parseResponse(response, type, statusCode));
}

/**
* Sends current request blocking if necessary to get
* the response. Converts response body to specified type, if error happens during conversion
* throws {@link coresearch.cvurl.io.exception.ResponseMappingException}.
*
* @param type type of object to convert response body.
* @param <T> type of object to convert response body
* @return object of specified type
*/
@Override
public <T> Optional<T> asObject(BodyType<T> type, int statusCode) {
return sendRequestAndWrapInOptional(getStringBodyHandler(),
(response) -> parseResponse(response, type, statusCode));
}

@Override
public <T> T asObject(Class<T> type) {
try {
Expand All @@ -168,38 +116,26 @@ public <T> T asObject(Class<T> type) {
}
}

/**
* Sends current request blocking if necessary to get
* the response.
*
* @return {@link Optional} with response if request no error happened during
* request sending or empty {@link Optional} otherwise.
*/
@Override
public <T> T asObject(BodyType<T> type) {
try {
return sendRequest(getStringBodyHandler(),
response -> genericMapper.readResponseBody(new Response<>(response), type));
} catch (IOException | InterruptedException e) {
throw new RequestExecutionException(e.getMessage(), e);
}
}

@Override
public Optional<Response<String>> asString() {
return sendRequestAndWrapInOptional(getStringBodyHandler(), Response::new);
}

/**
* Sends current request blocking if necessary to get
* the response as {@link InputStream}
*
* @return {@link Optional} with response if request no error happened during
* request sending or empty {@link Optional} otherwise.
*/
@Override
public Optional<Response<InputStream>> asStream() {
return sendRequestAndWrapInOptional(getStreamBodyHandler(), Response::new);
}

/**
* Sends current request blocking if necessary to get
* the response with body parsed by provided bodyHandler.
*
* @param bodyHandler used to parse response body
* @return {@link Optional} with response if request no error happened during
* request sending or empty {@link Optional} otherwise.
*/
@Override
public <T> Optional<Response<T>> as(HttpResponse.BodyHandler<T> bodyHandler) {
return sendRequestAndWrapInOptional(bodyHandler, Response::new);
Expand All @@ -224,13 +160,21 @@ private <T, U> Optional<T> sendRequestAndWrapInOptional(HttpResponse.BodyHandler
}

private <T> T parseResponse(HttpResponse<String> response, Class<T> type, int statusCode) {
checkIfStatusCodesAreEqual(response, statusCode);
return genericMapper.readValue(response.body(), type);
}

private <T> T parseResponse(HttpResponse<String> response, BodyType<T> type, int statusCode) {
checkIfStatusCodesAreEqual(response, statusCode);
return genericMapper.readValue(response.body(), type);
}

private void checkIfStatusCodesAreEqual(HttpResponse<String> response, int statusCode) {
if (response.statusCode() != statusCode) {
throw new UnexpectedResponseException("Received response with status code: " + response.statusCode() +
",expected: " + statusCode + ";Response: " + response.body(),
new Response<>(response));
}

return genericMapper.readValue(response.body(), type);
}

private <T, U> T sendRequest(HttpResponse.BodyHandler<U> bodyHandler,
Expand Down
Loading

0 comments on commit 6ba7bc3

Please sign in to comment.