Spring HttpServerErrorException custom response body not being serialized - java

I have a Controller like this example:
#RestController
#RequestMapping(value = "/risk", produces = {MediaType.APPLICATION_JSON_VALUE})
public class CalculationController {
#RequestMapping(value = "/calculate", method = RequestMethod.POST)
public CalculationResult calculate(InputFields i) {
try {
return calcService.calculate(i);
} catch (CustomException custEx) {
throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR,
null,
null,
getReportLogAsBytes(custEx), //for some reason not working when serialized in Json
Charset.defaultCharset());
}
}
private byte[] getReportLogAsBytes(CustomException e) {
try {
return objectMapper.writeValueAsBytes(e.getReportLog()); //
} catch (JsonProcessingException e1) {
throw new RuntimeException("Unable to serialize Simulation report log to bytes ", e1);
}
}
class CustomException extends Exception {
private List<String> reportLog;
public CustomException(List<String> reportLog) {
super();
this.setReportLog(reportLog);
}
public List<String> getReportLog() {
return reportLog;
}
public void setReportLog(List<String> reportLog) {
this.reportLog = reportLog;
}
}
}
When posting the inputs to the controller and a CustomException occurs, I instantiate HttpServerErrorException using the constructor that accepts a byte array for responseBody. I basically give it a list of String error messages converted to byte array.
The problem is the response body still does not show the list of errors messages I passed to it. I tried looking for examples on using HttpServerErrorException with response body but cannot seem to find any... Any help would be greatly appreciated.

You throw your HttpServerErrorException but don't handle it in the proper way.
Read this: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

Related

How to write generic method to return list of objects in a restassured reponse

I am trying to declare a method and a constructor to RestResponse class in case of the json returns a list of DTOs directly , or probably make the existing constructor smarter to differentiate if the rest response is a List or one single element.
public class RestResponse<T> implements IRestResponse<T> {
public static ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
private T data;
private Response response;
private Exception e;
// constructor
public RestResponse(Class<T> t, Response response) {
this.response = response;
try {
this.data = t.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("There should be a default constructor in the Response POJO");
}
// getBody method
public T getBody() {
try {
data = (T) response.getBody().as(data.getClass());
} catch (Exception e) {
this.e = e;
}
return data;
}
}
}
This implementation is very Ok when the return response is a single DTO like this:
{
"attribute1":"value1",
"attribute2":"value2",
"attribute3":"value3"
}
or even like this:
{
"attribute1":"value1",
"attribute2":"value2",
"attribute3":[
{ "att1":"val1",
"att2":"val2"
}]
}
but I can't get a generic solution to get a method when the return is like:
[
{
"attribute1":"value1",
"attribute2":"value2",
"attribute3":"value3"
},{
"attribute1":"value1",
"attribute2":"value2",
"attribute3":"value3"
}
]

Unable to cover catch block code coverage

I am not able to cover below HttpClientErrorException catch block in Junit code coverage. How can I do this?
code
#Autowired
private ExceptionHandlerService errorHandler;
public CartResponse callUpdateCart(AddToCartRequest request) {
String url = Utils.formatHttpUrl(cartbaseUrl, CART_UPDATE_CART);
try {
HttpEntity<?> entity = new HttpEntity<>(request);
JsonNode jsonNode = restTemplate.postForObject(url, entity, JsonNode.class);
if (jsonNode.has(Constants.CONTENT) && !jsonNode.path(Constants.CONTENT).path(Constants.PAYLOAD).isMissingNode()) {
jsonNode = jsonNode.path(Constants.PAYLOAD).get(Constants.PAYLOAD);
} else {
errorHandler.error(ErrorMessages.EPO_VALIDATEQ_ERROR_08, jsonNode);
}
return JsonService.getObjectFromJson(jsonNode, CartResponse.class);
} catch (HttpClientErrorException e) {
errorHandler.error(ErrorMessages.EPO_VALIDATEQ_ERROR_08, e.getResponseBodyAsString());
return null;
} catch (HttpServerErrorException e) {
throw new ServiceException(ErrorMessages.EPO_SYSTEM_ERROR, e.getMessage(), url);
}
}
ExceptionHandlerService
#Override
public void error(ResolvableErrorEnum error, String responseBody) {
JsonNode response = JsonService.getObjectFromJson(responseBody, JsonNode.class);
if (null != response && null != response.get(Constants.ERROR)) {
ServiceError serviceError = JsonService.getObjectFromJsonNode(response.get(Constants.ERROR), ServiceError.class);
error(error, serviceError.getErrorId(), serviceError.getMessage());
}
throw new ServiceException(error);
}
junit
#Test(expected = ServiceException.class)
public void test_callUpdateCart_Exception() throws IOException {
AddToCartRequest req = createAddToCartRequest();
String responseBodyStr = "{\"error\":{\"errorId\":\"Service-I-1003\",\"message\":\"Error returned from downstream system.\",\"traceCode\":\"CART;400\",\"details\":[{\"code\":\"400\",\"message\":\"400 Bad Request\"},{\"code\":\"DTV_CAT_ERR_002\",\"message\":\"Error in getting response from catalog.\",\"traceCode\":\"CART;400\"}]}}\r\n";
byte[] body = responseBodyStr.getBytes(StandardCharsets.UTF_8);
HttpClientErrorException e = new HttpClientErrorException(HttpStatus.BAD_REQUEST, "BAD REQUEST", body,
StandardCharsets.UTF_8);
when(restTemplate.postForObject(Mockito.anyString(), Mockito.<HttpEntity<?>>any(), Mockito.eq(JsonNode.class)))
.thenThrow(e);
client.callUpdateCart(req);
}
error
It seems like the mockito.when() is not working properly, since it looks like it is not throwing the exception you are asking it to throw. I had similar issues and tinkering with mockito matchers usually fixes them.
You can check this page for a bit more information regarding "expanding" or restricting your argument matchers.
I think the solution bellow will work:
when(restTemplate.postForObject(Mockito.anyString(), Mockito.any(HttpEntity.class), Mockito.any(JsonNode.class)))
.thenThrow(e);

How to return error message from a method that otherwise returns a collection

It's more of a conceptual thing. My method is supposed to return a list of Conferences. But if there is an error, I just want it to send a String response or maybe a JSON response like {err: 'Some error'}.Offcourse following method throws compiler error for this line - return e.getMessage(); . How to achieve this?
#RequestMapping(value = "/api/allconf", method = RequestMethod.GET)
public List<Conferences> getAllConf(#RequestBody Conferences conf) {
List<Conferences> allConf = new ArrayList<Conferences>();
try {
allConf.addAll(confRepository.findAll());
} catch(Exception e){
return e.getMessage();
}
return allConf;
}
e.getMessage() returns a String and you method is a Conferences List, use a new generic response class like
public class Response {
private Object content;
private String error;
// getters and setters
}
and change your method
#RequestMapping(value = "/api/allconf", method = RequestMethod.GET)
public Response getAllConf(#RequestBody Conferences conf) {
Response resp = new Response();
List<Conferences> allConf = new ArrayList<Conferences>();
try{
allConf.addAll(confRepository.findAll());
resp.setContent(allConf);
}catch(Exception e){
resp.setError(e.getMessage());
}
return resp;
}
Have one option:
Best solution it is throw an exception:
#RequestMapping(value = "/api/allconf", method = RequestMethod.GET)
public List<Conferences> getAllConf(#RequestBody Conferences conf) {
List<Conferences> allConf = new ArrayList<Conferences>();
try {
allConf.addAll(confRepository.findAll());
} catch(Exception e){
throw new IllegalArgumentException(e.getMessage());
}
return allConf;
}
And create an error handler to handle exception and how you wanna display it:
#ControllerAdvice
public class CustomErrorHandler {
#ExceptionHandler(IllegalArgumentException.class)
public void handlerIllegalArgumentException(IllegalArgumentException exception, ServletWebRequest webRequest) throws IOException {
webRequest.getResponse().sendError(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
}
}

AWS DynamoDB - converter class - "Bad request, unable to parse JSON"

I've made a generic method that is convertor class for complex classes and 2nd one for enums. I have Recipe class that is complex so I used #DynamoDBTypeConverted(converter = ObjectConverter.class)
This is my converter class:
public class ObjectConverter<T extends Object> implements DynamoDBTypeConverter<String, T> {
ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convert(T object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
throw new IllegalArgumentException("Unable to parse JSON");
}
#Override
public T unconvert(String object) {
try {
T unconvertedObject = objectMapper.readValue(object, new TypeReference<T>() {
});
return unconvertedObject;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
This is convertor class for enums:
public class EnumConverter<T extends Object> implements DynamoDBTypeConverter<String, List<T>> {
#Override
public String convert(List<T> objects) {
//Jackson object mapper
ObjectMapper objectMapper = new ObjectMapper();
try {
String objectsString = objectMapper.writeValueAsString(objects);
return objectsString;
} catch (JsonProcessingException e) {
//do something
}
return null;
}
#Override
public List<T> unconvert(String objectsString) {
ObjectMapper objectMapper = new ObjectMapper();
try {
List<T> objects = objectMapper.readValue(objectsString, new TypeReference<List<T>>() {
});
return objects;
} catch (JsonParseException e) {
//do something
} catch (JsonMappingException e) {
//do something
} catch (IOException e) {
//do something
}
return null;
}
The problem is when I try to test CRUDs methods.. I have addProduct method and this one works fine, I created addRecipe method and it looks almost the same, but here I have problem while posting in Postman i got an error: "Bad request, unable to parse JSON".
And information from log file:
"Can not deserialize instance of java.util.ArrayList out of START_OBJECT token at [Source: {"id":null,"name":"test","labels":["GLUTEN_FREE"],"author":{"name":"Plejer Annołn","id":"testID2"},"media":{"name":"heheszki","url":"http://blabla.pl","mediaType":"IMAGE"},"recipeElements":{"product":{"id":927c3ed3-400b-433d-9da0-1aa111dce584,"name":"bąkiKacpraNieŚmierdzą","calories":1000,"fat":400.0,"carbo":20.0,"protein":40.0,"productKinds":["MEAT"],"author":{"name":"Plejer Annołn","id":"testID2"},"media":{"name":"heheszki","url":"http://blabla.pl","mediaType":"IMAGE"},"approved":false},"weight":"100"},"approved":false}; line: 1, column: 190] (through reference chain: pl.javamill.model.kitchen.Recipe["recipeElements"])"
What can be wrong?
The methods in the converter class are always returning a value even if exceptions are thrown (unless they are RuntimeExceptions), though they may not be correctly marshaling/unmarshaling the Product in RecipeElement. A better alternative is to annotate the getRecipeElement() method in your class with #DynamoDBTypeConvertedJson, that provides out-of-the-box JSON marshaling/unmarshaling. It may be something to do with the HTTP request you are sending in Postman too. You should add more information on the getProduct(), setProduct() methods and the actual postman request (without any sensitive information).

Sonar Complaint Java + Either remove this useless object instantiation of class "ResponseEntity" or use it

My team implemented Sonar code coverage for my project and one the method in class complaining about the error 'Either remove this useless object instantiation of class "ResponseEntity" or use it '. If i remove the line it is complaining it will work. But i want to handle that error as well.
Any suggestions how this to be handled will be appreciated
#RequestMapping(value = "/**/identity", method = RequestMethod.POST)
public ResponseEntity<String> createIdentity(#RequestBody #NotNull Heartbeat heartbeat) {
//Validate
if (StringUtils.isEmpty(heartbeat.getHostname())
|| StringUtils.isEmpty(heartbeat.getEnvironment())
|| StringUtils.isEmpty(heartbeat.getProcessSignature())) {
return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
}
try {
byte[] encodedValue = identityService.createIdentity(heartbeat.getHostname(), heartbeat.getEnvironment(),
heartbeat.getProcessSignature());
return ResponseEntity.ok(new String(encodedValue));
} catch (BadPaddingException | IllegalBlockSizeException e) {
log.error("Unable to create entity for the request", e);
new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); // Sonar Complaint
}
return ResponseEntity.ok().build();
}
The problem is you are not returning the response that you think you are in Error scenario. You can solve this by correctly returning the INTERNAL_SERVER_ERROR response.
Sonar is just pointing out that probable flaw.
#RequestMapping(value = "/**/identity", method = RequestMethod.POST)
public ResponseEntity<String> createIdentity(#RequestBody #NotNull Heartbeat heartbeat) {
//Validate
if (StringUtils.isEmpty(heartbeat.getHostname())
|| StringUtils.isEmpty(heartbeat.getEnvironment())
|| StringUtils.isEmpty(heartbeat.getProcessSignature())) {
return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
}
try {
byte[] encodedValue = identityService.createIdentity(heartbeat.getHostname(), heartbeat.getEnvironment(),
heartbeat.getProcessSignature());
return ResponseEntity.ok(new String(encodedValue));
} catch (BadPaddingException | IllegalBlockSizeException e) {
log.error("Unable to create entity for the request", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return ResponseEntity.ok().build();
}

Categories