How to handle multiple possible response types with Retrofit2, Gson and Rx - retrofit2

The API i have to use sucks, and always returns HTTP 200. But sometimes there is proper response:
[{"blah": "blah"}, {"blah": "blah"}]
and sometimes, there is error:
{"error": "Something went wrong", "code": 123}
I'm using Retrofit2 with Gson converter and Rx adapter:
final Api api = new Retrofit.Builder()
.baseUrl(URL)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(Api.class);
And now, when I receive error response, the onError handler is called with following exception:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:211)
at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.Observable.unsafeSubscribe(Observable.java:10144)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:230)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
How can I solve it? If I could get the response in the onError handler, I could reparse it with proper error model class. But it seems I can't get the raw response.

You can use a custom Gson deserializer to marshal both responses into a single object type. Here is a rough sketch of the idea assuming your current response type is List<Map<String, String>>, you will need to adjust based on your actual return type. I am also making the assumption that the API always returns an array on success --
public class MyResponse {
String error;
Integer code;
List<Map<String, String>> response;
}
interface MyApi {
#GET("/")
Observable<MyResponse> myCall();
}
private class MyResponseDeserializer implements JsonDeserializer<MyResponse> {
public MyResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
MyResponse response = new MyResponse();
if (json.isJsonArray()) {
// It is an array, parse the data
Type responseType = new TypeToken<List<Map<String, String>>>(){}.getType();
response.response = context.deserialize(json, responseType);
} else {
// Not an array, parse out the error info
JsonObject object = json.getAsJsonObject();
response.code = object.getAsJsonPrimitive("code").getAsInt();
response.error = object.getAsJsonPrimitive("error").getAsString();
}
return response;
}
}
Use the above to create a custom Gson
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyResponse.class, new MyResponseDeserializer())
.create();
use that in your retrofit builder --
.addConverterFactory(GsonConverterFactory.create(gson))
You should also update your interface to return Observable<MyResponse>. You will get both success and error in onNext now. You'll need to inspect the object to determine if it is a successful response (response != null) or not.

Related

Spring Integration Java DSL and Http.outboundGateway: How to get the real error message JSON

How to get the real error message JSON when the Http.outboundGateway call is failed.
For example my program does the HTTP POST. The operation fails with the error code 400 Bad Request and the real error message is (tested with the Postman):
{
"name": [
"This field is needed."
]
}
I have the error channel like this:
#Bean
private IntegrationFlow myErrorChannel() {
return f -> f.handle("myErrorHandler", "handle")
....
;
}
and the Class MyErrorHandler is like this:
#Service
public class MyErrorHandler {
#ServiceActivator
public Message<MessageHandlingException> handle(Message<MessageHandlingException> message) {
...
}
}
Does the MessageHandlingException contain the real error message?
{
"name": [
"This field is needed."
]
}
I debugged the code and checked the MessageHandlingException exception and it seems it doesn't contain the real error message. The detailMessage contains the text 400 Bad Request, but I want to know the real error message.
How to get the real error message?
Edit:
This is working (I'm assigning the real error message to the new payload):
final RestClientResponseException clientException = (RestClientResponseException) messagingHandlingException.getCause();
payload = clientException.getResponseBodyAsString();
The Resttemplate uses a DefaultResponseErrorHandler by default. That one has a logic like:
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = getResponseBody(response);
Charset charset = getCharset(response);
switch (statusCode.series()) {
case CLIENT_ERROR:
throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);
case SERVER_ERROR:
throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);
default:
throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);
}
}
An exception from here is thrown to the HttpRequestExecutingMessageHandler which really wraps it into the MessageHandlingException.
Since you say that you can handle the last one via your MyErrorHandler, I would suggest you just take a look into the cause of the MessageHandlingException, and you'll that RestClientResponseException with all the required info from the response.

OpenWeather api returing null values

I am trying to fetch data from openweather api using retrofit2 and Gson but it always return the null value error.
I tried to debug and the response returned is fine yet it returns null.
Here is my code..
POJO CLASSES was taken from here.
http://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=b6907d289e10d714a6e88b30761fae22
How I am trying to fetch.
API interface
public interface Api {
String api_key = "b6907d289e10d714a6e88b30761fae22";
String BASE_URL= "http://api.openweathermap.org/data/2.5/";
#GET("weather")
Call<Weather> getWeather(#Query("lat") String latitude, #Query("lon") String longitude, #Query("appid") String api_key);
}
LoadWeather()
public void LoadWeather()
{
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
Api api = retrofit.create(Api.class);
Call<Weather> weatherCall = api.getWeather("12.9716", "77.5946", api_key);
weatherCall.enqueue(new Callback<Weather>() {
#Override
public void onResponse(Call<Weather> call, Response<Weather> response) {
Weather weather = response.body();
String temp = weather.getList().get(0).getMain().getTemp().toString();
Log.d(TAG,"onReponse :"+weather);
}
#Override
public void onFailure(Call<Weather> call, Throwable t) {
}
});
}
You are using the App Key 'b6907d289e10d714a6e88b30761fae22' which is used for
the examples (samples.openweathermap.org).
You have to subscribe to get your own API key.
Is it possible you forgot to include which API you want to use in the URL?
Maybe you need:
String BASE_URL= "http://api.openweathermap.org/data/2.5/weather?";

Spring Integration DSL transformer

Can I have some help here on the below issue:
Calling the transformer to transform the input object to Map Object and calling handler, the handler is missing header values added before.
Why on transforming payload to Map object losing all headers?
//Adding header here setHeader("t", "t");
#ResponseBody
public EmResponse getAuditTrail(#Valid #RequestBody NGAuditTrailEntry auditEntry) {
LOG.info("Audit Service Called, creating new audit " + auditEntry);
AuditCreationFlow.CreateAuditGateway auditGateway = applicationContext.getBean(AuditCreationFlow.CreateAuditGateway.class);
MessageBuilder messageBuilder = MessageBuilder.withPayload(auditEntry).setHeader("t", "t");
Object response = auditGateway.createAudit(messageBuilder.build());
EmResponse res = new EmResponse();
LOG.info("Done with Audit creation. Response " + response);
return res;
}
//Integration flow starts here
public IntegrationFlow createAuditGatewayFlow() {
LOG.debug("Entered to spring integration flow to create the Audit entry");
return IntegrationFlows.from("auditInputChannel")
.handle(auditObjTransformer, "transformToEjbCompatible")
.handle(ejbCaller, "callEjb")
.get();
}
//Transforming payload object to map
#Component
public class AuditObjTransformer {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
#Transformer
public Object transformToEjbCompatible(NGAuditTrailEntry ngAuditTrailEntry, Map<String, Object> headers) {
LOG.debug("Transforming the NGAuditTrailEntry To AuditEntry object which is EJB compatible");
//#TODO - Tranformation code goes here.
String s = ngAuditTrailEntry.getObjectName();
Map<String, String> m = new HashMap<>();
m.put("x", s);
return m;
}
//Here in this handler, not getting headers what I added in the rest service above.
public class EJBCaller {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
public Object callEjb(Object payload, Map<String, Object> headers) throws EJBResponseException {
LOG.debug("Calling Audit EJB to crated Audit entry.");
//#TODO EJB calling code goese here.
LOG.debug("Returned from EJB after creating Audit entry. Returned value" + payload);
return payload;
}
If the transform is other than map then no issues in headers.
Thanks,
Siva
callEjb(Object payload, Map<String, Object> headers)
If payload is a Map, you have that payload in the payload and the headers method arguments at the same time.
To make it working and carry exactly headers to that Map argument you should use #Headers annotation on it:
* Annotation which indicates that a method parameter should be bound to the headers of a
* message. The annotated parameter must be assignable to {#link java.util.Map} with
* String keys and Object values.

Content-Type must be 'application/json-patch+json' JsonServiceClient ServiceStack

I'm trying to perform a patch with a JsonServiceClient to a service stack api as follows:
var patchRequest = new JsonPatchRequest
{
new JsonPatchElement
{
op = "replace",
path = "/firstName",
value = "Test"
}
};
_jsonClient.Patch<object>($"/testurl/{id}", patchRequest);
But I'm getting the following error:
Content-Type must be 'application/json-patch+json'
The error is clear. Is there a way to change the content type before perform the request for the JsonServiceClient?
This is the request POCO in the ServiceStack api:
[Api("Partial update .")]
[Route("/testurl/{Id}”, "PATCH")]
public class PartialTest : IReturn<PartialTestRequestResponse>, IJsonPatchDocumentRequest,
IRequiresRequestStream
{
[ApiMember(Name = “Id”, ParameterType = "path", DataType = "string", IsRequired = true)]
public string Id { get; set; }
public Stream RequestStream { get; set; }
}
public class PartialTestRequestResponse : IHasResponseStatus
{
public ResponseStatus ResponseStatus { get; set; }
}
Service implementation:
public object Patch(PartialTest request)
{
var dbTestRecord = Repo.GetDbTestRecord(request.Id);
if (dbTestRecord == null) throw HttpError.NotFound("Record not found.");
var patch =
(JsonPatchDocument<TestRecordPoco>)
JsonConvert.DeserializeObject(Request.GetRawBody(), typeof(JsonPatchDocument<TestRecordPoco>));
if (patch == null)
throw new HttpError(HttpStatusCode.BadRequest, "Body is not a valid JSON Patch Document.");
patch.ApplyTo(dbTestRecord);
Repo.UpdateDbTestRecord(dbTestRecord);
return new PartialTestResponse();
}
I'm using Marvin.JsonPatch V 1.0.0 library.
It's still not clear where the Exception is coming from as it's not an Error within ServiceStack. If you've registered a Custom Format or Filter that throws this error please include its impl (or a link to it) as well as the full StackTrace which will identify the source of the error.
But you should never call Patch<object> as an object return type doesn't specify what Response Type to deserialize into. Since you have an IReturn<T> marker you can just send the Request DTO:
_jsonClient.Patch(new PartialTest { ... });
Which will try to deserialize the Response in the IReturn<PartialTestRequestResponse> Response DTO. But as your Request DTO implements IRequiresRequestStream it's saying you're expecting unknown bytes that doesn't conform to a normal Request DTO, in which case you likely want to use a raw HTTP Client like HTTP Utils, e.g:
var bytes = request.Url.SendBytesToUrl(
method: HttpMethods.Path,
requestBody: jsonPatchBytes,
contentType: "application/json-patch+json",
accept: MimeTypes.Json);
You could modify the ContentType of a JSON Client using a request filter, e.g:
_jsonClient.RequestFilter = req =>
req.ContentType = "application/json-patch+json";
But it's more appropriate to use a low-level HTTP Client like HTTP Utils for non-JSON Service Requests like this.

Spring integration, Setting error-handler on http outbound gateway

If I add a custom error-handler to an int-http:outbound-gateway, the response body is not unmarshalled according to the expected-response-type, instead I only get a ResponseEntity returned. My custom error handler is pretty simple:
public class MyResponseErrorHandler extends DefaultResponseErrorHandler {
private static final Logger log = LoggerFactory.getLogger(AlmaGetUserResponseErrorHandler.class);
#Override
public boolean hasError(final ClientHttpResponse response) throws IOException {
// stop http 400 from returning true to error here.
log.debug("Request has returned error code {}", response.getStatusCode());
if (response.getBody() != null) {
String returnBody = IOUtils.toString(response.getBody(), "UTF-8");
log.debug("Checking error from response, code {}, body {}", response.getStatusCode(), returnBody);
}
return false;
}
}
As soon as I remove the error-handler, it unmarshalls the XML response into my POJO correctly.
The issue above was that the MyResponseErrorHandler class was streaming out the body content before it was being passed to the marshaller for expected-response-type. Hence the body was null and a plain ResponseEntity was returned.

Resources