I have implemented a wrapper over OkHttpClient
. Which has two classes Get
and Post
that can be executed to get response of type specified with the instance of Get
and Post
. It looks like below,
HttpMethod
is the base class looks like
@Data
@NoArgsConstructor
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type",
visible = true
)
@JsonSubTypes({@JsonSubTypes.Type(
value = Get.class,
name = HttpMethodType.GET_NAME
),@JsonSubTypes.Type(
value = Post.class,
name = HttpMethodType.POST_NAME
)})
public abstract class HttpMethod<R> {
protected static final int DEFAULT_TIMEOUT_IN_MILLIS = 60000; // 60 seconds
private HttpMethodType type;
protected HttpConfiguration httpConfiguration;
// Start path with a `/`, also include query string
protected String path;
protected Map<String, String> headers;
private TypeReference<R> outputType;
protected HttpMethod(HttpMethodType type,
@Valid HttpConfiguration httpConfiguration,
Map<String, String> headers,
TypeReference<R> outputType,
String path) {
this.type = type;
this.httpConfiguration = httpConfiguration;
this.headers = headers;
this.outputType = outputType;
this.path = path;
}
/**
* Object will be parsed object of type R if type is not null, else it will be string
*/
public R execute() throws IOException {
Response response = executeCall();
return parse(response);
}
protected abstract Response executeCall() throws IOException;
protected GenericErrorResponse parseUnsuccessfulResponse(Response response) {
if (!response.isSuccessful()) {
try (ResponseBody responseBody = response.body()) {
String json = responseBody.string();
return MapperUtils.deserialize(json, GenericErrorResponse.class);
} catch (IOException e) {
throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
}
}
throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
}
protected R parse(Response response) {
if (!response.isSuccessful()) {
GenericErrorResponse errorResponse = parseUnsuccessfulResponse(response);
throw TarkshalaServiceException.create(NonSuccessResponseErrorCode.getInstance(response.code(), errorResponse.getErrorCode()));
}
try (ResponseBody responseBody = response.body()) {
String json = responseBody.string();
if (outputType != null) {
return MapperUtils.deserialize(json, outputType);
}
throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
} catch (IOException e) {
throw TarkshalaServiceException.create(SerializationErrorCode.getInstance());
}
}
public String getUrl() {
return String.format("%s%s", httpConfiguration.url(), path);
}
}
Get
and Post
implement it as following
@Data
@NoArgsConstructor
public class Get<R> extends HttpMethod<R> {
@Builder
public Get(@Valid HttpConfiguration httpConfiguration,
Map<String, String> headers,
TypeReference<R> outputType,
String path) {
super(HttpMethodType.GET, httpConfiguration, headers, outputType, path);
}
protected Response executeCall() throws IOException {
OkHttpClient client = new OkHttpClient()
.newBuilder()
.callTimeout(DEFAULT_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS)
.build();
Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url(getUrl());
for (Map.Entry<String, String> entry : headers.entrySet()) {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
Request request = requestBuilder.get().build();
return client.newCall(request).execute();
}
}
@Data
@NoArgsConstructor
public class Post<R> extends HttpMethod<R> {
private Object requestBody;
@Builder
public Post(HttpConfiguration httpConfiguration,
Map<String, String> headers,
TypeReference<R> outputType,
String path,
Object requestBody) {
super(HttpMethodType.POST, httpConfiguration, headers, outputType, path);
this.requestBody = requestBody;
}
@Override
protected Response executeCall() throws IOException {
OkHttpClient client = new OkHttpClient()
.newBuilder()
.callTimeout(DEFAULT_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS)
.build();
Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url(getUrl());
for (Map.Entry<String, String> entry : headers.entrySet()) {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
RequestBody body = RequestBody.create(okhttp3.MediaType.parse(MediaType.APPLICATION_JSON), MapperUtils.serializeAsJson(this.requestBody));
Request request = requestBuilder.post(body).build();
return client.newCall(request).execute();
}
}
I want to serialize and deserialize these, in order to store these to db and send over wire. But I am getting following error while deserializing
ERROR [2023-05-28 02:13:21,576] io.dropwizard.jersey.jackson.JsonProcessingExceptionMapper: Error handling a request: 3cddc3a0922e1cbe
! com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.fasterxml.jackson.core.type.TypeReference` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
! at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 11, column: 19] (through reference chain: com.tarkshala.tavahot.ms.clockwork.models.ScheduleJobRequest["httpRequest"]->com.tarkshala.common.http.Get["outputType"])
! at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
! at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904)
! at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
! at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1349)
! at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
! at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
I understand that this is happening because Jackson need Concrete class to deserialize. Is there a way around to achieve this?