Accepting multiple payloads from a Retrofit API request - java

My Retrofit API method is currently accepting one payload structure. However, the backend may return a different payload structure if there's any error in the request.
For example:
public void search(String term, final CallBack <ArrayList<String>> callBack) {
RetroGenerator.createService(APIServices.class).search(term).enqueue(new Callback<ArrayList<String>> () {
#Override
public void onResponse(Call<ArrayList<String>> call, Response<ArrayList<String>> response) {
if (response.isSuccessful()) {
callBack.onSuccess(response.body());
}
return;
}
callBack.onError();
}
#Override public void onFailure(Call<ArrayList<String>> call, Throwable t) {
callBack.onError();
}
});
}
The backend is returning an array of String values. However if an error occurs, backend may return the following payload structure:
{
"error": "Term can't be empty",
"code": 403
}
But the way my API method is setup, it only accepts one java model.
API Interface:
#FormUrlEncoded
#POST("api/v1/search.json")
Call<ArrayList<String>> search(#Field("term") String term);
Currently it's accepting only an ArrayList<String> and does not accept the custom error payload model. Given that I create a new model called Error:
public class Error {
public String error;
public int code;
}
How can I switch the retrofit API method's model when an error occurs?

You can have an ErrorUtils class to handle your unsuccessful responses:
public class ErrorUtils {
public static ApiError parseError(Response<?> response) {
Converter<ResponseBody, ApiError> converter = ServiceGenerator.retrofit().
responseBodyConverter(ApiError.class, new Annotation[0]);
ApiError apiError;
try {
apiError = converter.convert(response.errorBody());
} catch (IOException e) {
apiError = new ApiError();
}
return apiError;
}
}
Then when you find an unsuccessful response, just parse the response with the ErrorUtils class:
if (!response.isSuccessful()) {
// ...
ApiError apiError = ErrorUtils.parseError(response);
}
The ApiError class:
public class ApiError {
#SerializedName("error")
private String mErrorDescription;
#SerializedName("code")
private Integer mErrorCode;
public ApiError() {}
public void setErrorCode(Integer code) {
this.mErrorCode = code;
}
public Integer getErrorCode() {
return mErrorCode;
}
public String getErrorDescription() {
return mErrorDescription;
}
public void setErrorDescription(String errorDescription) {
mErrorDescription = errorDescription;
}
}

Related

Control errors on Retrofit calls like (404)

I want to get the HttpResponse code and control the errors for display them to the user.
I have my static retrofit request:
public class NetworkClient {
//KEY: f663e4c56cc039c837109c82c78bbd69
public static Retrofit getRetrofit(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.themoviedb.org/3/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// .client(OkHttpClient())
.build();
return retrofit;
}
And observable who takes the request returns me an observable:
public Observer<Response> getObserver(){
Observer<Response> response = new Observer<Response>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("test", "onSubscribe");
}
#Override
public void onNext(Response response) {
Log.d("test", "onNext");
fragmentInterface.showMovies(response);
}
#Override
public void onError(Throwable e) {
Log.d("test", "onError");
e.printStackTrace();
if(e instanceof HttpException){
int errorCode = ((HttpException) e).response().code();
}
}
#Override
public void onComplete() {
Log.d("test", "onComplete");
}
};
return response;
}
I see i can get the error from the onError, but i see i can have a class to get all type errors.

How to get specific error instead of Internal Server Error?

I am getting Internal Server Error on postman even though I am throwing a custom exception from my code exception.
I want to see the exception of having a valid error message and error code, what I am throwing. It will be a great help if anyone of you can help me on this point. Like how I can get a better error message. Adding below code snap.
Thanks in advance.
#Service
public class FetchActionImpl implements FetchAction {
private static Logger log = LoggerFactory.getLogger(FetchActionImpl.class);
#Autowired
FetchActionServiceImpl fetchActionService;// = new FetchActionServiceImpl();
#Override
public FetchResponse fetchActionRequest(String caseId) throws BBWException,Exception{
//String resp ="";
log.info("fetchaction Request: {}",ApplicationConstants.LOG_ENTRY_MESSAGE);
log.info("The caseId received from BRASS:\n {}",caseId);
FetchResponse resp = null;
try{
if(true) {
throw new BBWException("500","Test");
}
resp = fetchActionService.fetchIt(caseId);
log.debug("fetchaction Response: {}",resp.toString());
}
catch (BBWException be) {
throw be;
}
catch (Exception e) {
throw new BBWException("500",e.getMessage());
}
return resp;
}
}
#Api
#Path("/fetch_service")
public interface FetchAction {
#GET
#Path("/fetchaction/caseid/{caseid}")
#Produces({MediaType.APPLICATION_JSON})
//#Consumes({MediaType.TEXT_XML})
#ApiOperation(
value = "Respond BRASS Request",
notes = "Returns a XML object "
)
#ApiResponses(
value = {
#ApiResponse(code = 404, message = "Service not available"),
#ApiResponse(code = 500, message = "Unexpected Runtime error")
})
public FetchResponse fetchActionRequest(#PathParam("caseid") String caseid) throws BBWException, Exception;
}`
public class BBWException extends Exception {
private static final long serialVersionUID = -7987978270260338068L;
private String errorCode;
private String errorMessage;
public BBWException(String errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
#Override
public String toString() {
return (this.errorCode + " " + this.errorMessage);
}
}
Each time the (uncaught) exception is thrown, SpringBoot returns Http-500 Internal Server Error. There are many ways of handling exceptions in Spring.
Let's say I have my controller and I implicitly throw an exception.
#RestController
public class HelloWorldController {
#GetMapping("/hello-world")
public String helloWorld() {
throw new MyCustomException("I just can't say hello!");
}
}
It's the same as yours - you can specify anything in the exception.
First:
One of the way of handling it, is to create a class with #ControllerAdvice annotation.
#ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> handlyMyCustomException(MyCustomException e) {
logger.error("error occurred {}", e);
return new ResponseEntity<>("Something happened: " + e.getMessage(), HttpStatus.I_AM_A_TEAPOT);
}
}
This way you are able to catch the exception of your choice (globally) and return the message with the HTTP Response Status of your choice, not neccessarily I_AM_A_TEAPOT
Second:
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> handlyMyCustomException(MyCustomException e) {
logger.error("error occurred {}", e);
return new ResponseEntity<>("Something happened: " + e.getMessage(), HttpStatus.I_AM_A_TEAPOT);
}
You could also create only method annotated with #ExceptionHandler in your controller class - but this is not global and is going to work only for this exact controller calls.
Result below:
Third:
Another way of dealing with exceptions is to create your own error .html files. If you place a file in resources/static/error/500.html it should be returned when the Http-500 Internal Server Error is thrown.

Best way to use retrofit response in several activies

I have a function searchForTrips() which sends an API request and fetch some response in following way.
private void searchForTrips(){
int departurePortId = PORT_ID_LIST.get(departurePort);
int returnPortId = PORT_ID_LIST.get(returnPort);
int pax= Integer.parseInt(noOfPassengers);
String departureDatePARSED = DEPARTURE_DATE_VALUES.get(departureDate);
String returnDatePARSED = RETURN_DATE_VALUES.get(departureDate);
Call<TripSearchResponse> call = apiService.searchAvailableTrips(TripType,departurePortId,returnPortId,departureDatePARSED,returnDatePARSED,pax);
call.enqueue(new Callback<TripSearchResponse>() {
#Override
public void onResponse(Call<TripSearchResponse> call, Response<TripSearchResponse> response) {
int statusCode = response.code();
switch(statusCode){
case 200:
default:
Snackbar.make(findViewById(android.R.id.content),"Error loading data. Network Error.", Snackbar.LENGTH_LONG).show();
break;
}
}
#Override
public void onFailure(Call<TripSearchResponse> call, Throwable t) {
Log.i(TAG, t.getMessage());
Snackbar.make(findViewById(android.R.id.content),"Error loading data. Network Error.", Snackbar.LENGTH_LONG).show();
}
});
}
The purpose is to make this callback function reusable so I can call it from several activities and get requested data as I need. What is the best way to implement this?
try this way, its dynamic way and easy to use:
Create Retforit Interface:
public interface ApiEndpointInterface {
#Headers("Content-Type: application/json")
#POST(Constants.SERVICE_SEARCH_TRIP)
Call<JsonObject> searchForTrip(#Body TripRequest objTripRequest);
}
Create Retrofit Class:
public class AppEndPoint {
private static Retrofit objRetrofit;
public static ApiEndpointInterface getClient() {
if (objRetrofit == null){
objRetrofit = new Retrofit.Builder()
.baseUrl(Constants.SERVER_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return objRetrofit.create(ApiEndpointInterface.class);
}
}
Create this helper Classes/Interfaces to hold web service callback:
public enum ResponseState {
SUCCESS,
FAILURE,
NO_CONNECTION
}
public enum RequestType {
SEARCH_FOR_TRIP // add name for each web service
}
public class Response {
public ResponseState state;
public boolean hasError;
public RequestType requestType;
public JsonObject result;
}
public interface RestRequestInterface {
void Response(Response response);
Context getContext();
}
public class ResponseHolder { used to hold the Json response could be changed as your response
#SerializedName("is_successful")
#Expose
private boolean isSuccessful;
#SerializedName("error_message")
#Expose
private String errorMessage;
public boolean isSuccessful() {
return isSuccessful;
}
public void setSuccessful(boolean successful) {
isSuccessful = successful;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
public class AppClient {
private static ApiEndpointInterface objApiEndpointInterface;
private static Response objResponse;
private static Call<JsonObject> objCall;
// implement new method like below for each new web service
public static void searchForTrip(TripRequest objTripRequest, RestRequestInterface objRestRequestInterface) {
objResponse = new Response();
objResponse.state = ResponseState.FAILURE;
objResponse.hasError = true;
objResponse.requestType = RequestType.SEARCH_FOR_TRIP; // set type of the service from helper interface
objApiEndpointInterface = AppEndPoint.getClient();
objCall = objApiEndpointInterface.searchForTrip(objTripRequest);
handleCallBack(objRestRequestInterface);
}
private static void handleCallBack(final RestRequestInterface objRestRequestInterface) {
objCall.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, retrofit2.Response<JsonObject> response) {
try {
ResponseHolder objResponseHolder = new Gson().fromJson(response.body(), ResponseHolder.class);
if (objResponseHolder.isSuccessful()) {
objResponse.state = ResponseState.SUCCESS;
objResponse.hasError = false;
objResponse.result = response.body();
} else {
objResponse.errorMessage = objResponseHolder.getErrorMessage();
}
objRestRequestInterface.Response(objResponse);
} catch (Exception objException) {
objResponse.errorMessage = objRestRequestInterface.getContext().getString(R.string.server_error);
objRestRequestInterface.Response(objResponse);
}
}
#Override
public void onFailure(Call<JsonObject> call, Throwable objThrowable) {
String errorMessage = "";
if (objThrowable instanceof IOException) {
errorMessage = objRestRequestInterface.getContext().getString(R.string.no_connection_error);
} else {
errorMessage = objRestRequestInterface.getContext().getString(R.string.server_error);
}
objResponse.errorMessage = errorMessage;
objRestRequestInterface.Response(objResponse);
}
});
}
}
then go to your activity of fragment and make the call like this:
public class MainActivity extends AppCompatActivity implements RestRequestInterface {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// initialize ids
// prepare to call web service
// 1.Initialize your object to be sent over web service
TripRequest objTripRequest = new TripRequest();
objTripRequest.id = 1;
// 2.Show loader
// 3.Make the call
AppClient.searchForTrip(objTripRequest, this);
}
#Override
public void Response(Response response) {
// hide loader
try {
if (response.state == ResponseState.SUCCESS && !response.hasError) {
// check the type of web service
if (response.requestType == RequestType.SEARCH_FOR_TRIP) {
// acces the return here from response.result
}
} else {
String errorMsg = response.hasError ? response.errorMessage : getString(R.string.no_connection_error);
// show the error to the user
}
} catch (Exception objException) {
// show the error to the user
}
}
#Override
public Context getContext() {
// do not forgit set the context here
// if fragment replace with getAcitvity();
return this;
}
}

REST POST HTTP JSON Objects 400 error Android

Since I got some bad reviews I am rewriting this question...
I have an HTTP REST server and a client (Android app). I have programmed several APIs that work just fine, however there is one that is giving me a 400 error, and if I put a breakpoint in the server, it does not even triggers it. So, I would like to understand why it fails :( ...
It is very simple, I have a value object called Alarm with a few attributes, that I want to POST to the server for registration of object in the database.
This is the output:
Callback failure for call to http://10.0.0.3:8080/...
java.io.IOException: Unexpected code Response{protocol=http/1.1, code=400, message=, url=http://10.0.0.3:8080/BiTrack_API/api/assets/registerAlarm}
at it.bitrack.fabio.bitrack.AlarmView$2$1.onResponse(AlarmView.java:438)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:135)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
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)
This is my client side Android button listener:
View.OnClickListener addAlarmAction = new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
alarm.setThreshold(Float.parseFloat(thresholdEditText.getText().toString()));
String alarmJson = j.makeJsonBodyForAlarmRegistration(alarm);
tagLinearLayout.setVisibility(view.GONE);
operatorLinearLayout.setVisibility(view.GONE);
thresholdLinearLayout.setVisibility(view.GONE);
assetSpinner.setSelection(0);
r.attemptAddNewAlarm(alarmJson,
new Callback() {
#Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override public void onResponse(Call call, Response response) throws IOException {
try (final ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
final String responseBodyString = responseBody.string();
final int resultCode = response.code();
try {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Log.i("BiTrack", "attemptAddNewAlarm RESULT: " + resultCode);
executeAlarmRegistration(resultCode);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
};
This is the code where I generate the Json body for the POST in the client:
public String makeJsonBodyForAlarmRegistration (Alarm alarm) {
Gson gson = new Gson();
String jsonAlarm = gson.toJson(alarm);
return jsonAlarm;
}
This is the actual POST code in the client (Android) side:
public void attemptAddNewAlarm(String json, Callback callback) throws Exception {
final OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(WEB_SERVER + "BiTrack_API/api/assets/registerAlarm")
.post(body)
.build();
client.newCall(request).enqueue(callback);
}
This is my server side code:
#POST
#Path("/registerAlarm")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response registerAlarm(Alarm alarm) {
System.out.println("Received API Call: registerAlarm for alarm tagId: " + alarm.getTagId() + " operatorId: " + alarm.getOperatorId() + " treshold: " + alarm.getThreshold());
DataProcessor dp = new DataProcessor();
AssetUpdateDAO aDAO = new AssetUpdateDAO();
ArrayList<Alarm> customerAlarms = aDAO.getUserAlarmsForAsset(alarm.getUserId(), alarm.getAssetId());
if (dp.isNewAlarmDuplicate(customerAlarms, alarm)) {
return Response.status(480).build(); // duplicated error
} else {
int response = aDAO.insertAssetUserAlarm(alarm.getUserId(), alarm.getAssetId(), alarm.getTagId(), alarm.getOperatorId(), alarm.getThreshold());
if (response == -5) {
return Response.status(484).build(); // something went wrong while inserting alarm into db
} else {
return Response.status(200).build();
}
}
}
This is my Alarm value object (identical class in client and server):
public class Alarm {
public Alarm() {
}
protected int id;
protected int userId;
protected int assetId;
protected int tagId;
protected int operatorId;
protected float threshold;
protected String networkAssetCode;
public String getNetworkAssetCode() {
return networkAssetCode;
}
public void setNetworkAssetCode(String networkAssetCode) {
this.networkAssetCode = networkAssetCode;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getAssetId() {
return assetId;
}
public void setAssetId(int assetId) {
this.assetId = assetId;
}
public int getTagId() {
return tagId;
}
public void setTagId(int tagId) {
this.tagId = tagId;
}
public int getOperatorId() {
return operatorId;
}
public void setOperatorId(int operatorId) {
this.operatorId = operatorId;
}
public float getThreshold() {
return threshold;
}
public void setThreshold(float threshold) {
this.threshold = threshold;
}
}
I really appreciate any help...
In order to help you, the endpoint code is required. Now it is even unclear what technology stack is used for your API.
But from the information that is present... The endpoint considers your json as invalid.
400 Bad Request
The request could not be understood by the server due to malformed
syntax. The client SHOULD NOT repeat the request without
modifications.
In jax-rs the payload is first deserialized before it reaches the method that is bound to the url en http method.
Possibly the deserializing is failing and it never reaches the breakpoint you set.
What would be interesting is the following:
logs or exception from the server. The client exception is not that helpful, since the server returns this response.
the actual (json) payload that is send over the wire.
what deserialization mechanism is used at the server end? Reflection or did you make your own deserializer?
I found the issue! After 48 hours looking for the impossible, discovered that I had done a small update at the object attribute at the server side that had not been replicated in the client side...

Java Rest Service Jersey User Defined Exception Handling

I have Created Java Rest service using Jersey Implementation.
My Requirement is to throw UserDefined Exception in my Rest Service
I have created Exception class and ExceptionMapper Classes
Here is my Java code:
public class AppServerException extends Exception {
private String errorMessage;
private int errorId;
private static final long serialVersionUID = 1L;
// Parameterless Constructor
public IT360AppServerException() {
}
// Constructor that accepts a message
public IT360AppServerException(String message, int errorid) {
super(message);
errorMessage = message;
errorId = errorid;
}
public String getErrorMessage() {
return errorMessage;
}
public int getErrorId() {
return errorId;
}
}
ExceptionMapper Class:
#Provider
public class AppServerExceptionMapper implements
ExceptionMapper<AppServerException> {
#Override
public Response toResponse(AppServerException exception) {
System.out.println("toResponse>>");
System.out.println("Error Code >>>>"+exception.getErrorId());////printing
System.out.println("Error Message >>>"+exception.getErrorMessage());//printing
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setErrorCode(exception.getErrorMessage());
errorResponse.setErrorId(exception.getErrorId());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(errorResponse).type(MediaType.APPLICATION_JSON).build();
}
}
Problem:
I am able to print the logs inside toResponse() method but the response is not returning.Flow is stopping at the logs itself.Could any one help
RestService:
#POST
#Path("/update")
#Consumes("application/json")
#Produces("application/json")
public Response updateIT30(String json,
#HeaderParam("authorization") String authString)
throws UserAuthenticationException, IT360AppServerException {
System.out.println(" JSON Format" + json);
try{
response = client.execute(post);
}
catch(ConnectException e){
throw new IT360AppServerException("App Server is down", 403);
}
}

Categories