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?