exception message for missing #RequestBody method parameter - json

Firstly,if you have any ideas or solution, thank you to present here.
I use #RequestBody on one of my controllers for a required parameter, I need some useful way of saying which parameter was missing if it's not there.
When there are some parameters missing, it will throw the NullPointerException, SO I create a new exception to instance of this null exception (please see the httpemun and the httphandler)
These are the primary codes which referring to this question.
my controller:
public ResponseEntity<?> createOrder(#RequestBody Cart cart) throws Exception {
// ......
}
my entity cart:
public class Cart{
private String channel
private String cartId;
private String status;
private String currency;
getters...
setters...
}
my Http emun class:
public enum HttpStatusEnum {
CRE_CART_INCOMPLETE_BODY(400,"E001","Incomplete request body","");
private HttpStatusEnum(int statusCode, String code,
String message, String detail) {
this.statusCode = statusCode;
this.code = code;
this.message = message;
this.detail = detail;
}
private final int statusCode;
private final String code;
private final String message;
private String detail;
public int getStatusCode() {
return statusCode;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getDetail() {
if(detail.isEmpty()) {
return message;
}else {
return detail;
}
}
}
I also have one exception handle
#ControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<Object> defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
if(e instanceof NullPointerException)
{if(req.getRequestURL().toString().contains(HttpSourceEnum.CART.toString()) && req.getMethod().equals(RequestMethod.POST.toString())){
errorBodyVo.setMessage(HttpStatusEnum.CRE_CART_INCOMPLETE_BODY.getMessage());
errorBodyVo.setDetails(HttpStatusEnum.CRE_CART_INCOMPLETE_BODY.getDetail());
errorBodyVo.setCode(HttpStatusEnum.CRE_CART_INCOMPLETE_BODY.getCode());
}else {
errorBodyVo.setMessage(HttpStatusEnum.COMMON_MISSING_FIELD.getMessage());
errorBodyVo.setDetails(HttpStatusEnum.COMMON_MISSING_FIELD.getDetail());
errorBodyVo.setCode(HttpStatusEnum.COMMON_MISSING_FIELD.getCode());
}
httpStatus = HttpStatus.BAD_REQUEST;
}
}
right now,
my API request is:
{
"channel": "XX",
"cartId": "109",
"status": "1",
}
I receive the API response just like below:
{
"error": {
"code": "E001",
"message": "Incomplete request body",
"details": ""
}
}
but it doesn't match my expect.
if the channel is missing in my request like below:
{
"cartId": "109",
"status": "1",
}
I expect to show "Required request body content is missing: Channel" in the details:
{
"error": {
"code": "E001",
"message": "Incomplete request body",
"details": "Required request body content is missing: Channel"
}
}
How could I do that? Thanks guys!

A better approach, if you can do it, would probably be to use JSR 303 Validation, which is probably included with your existing Spring dependencies, assuming you're using recent versions.
There's a good, if quite simple, tutorial here: https://www.mkyong.com/spring-mvc/spring-rest-api-validation/ and many more online with more details.
The official Spring docs on the subject are here: https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#validation-beanvalidation
You should only need to make changes to a few classes, and can potentially remove a lot of your exception handling, depending on how much control you want, or how much you're willing to go with Spring's defaults. The basic changes you'll need to make though are:
To tell Spring what validation is required, you add one or more annotations to the Cart class, for example, if you want to ensure that the channel is specified, you could do something like:
public class Cart{
#NotNull
private String channel
private String cartId;
private String status;
private String currency;
Then, to instruct Spring to validate the Cart object before passing it to your controller, you need to add an #Valid annotation to your controller method signature:
public ResponseEntity<?> createOrder(#Valid #RequestBody Cart cart) throws Exception {
Lastly, modify the createOrder signature again to accept an Errors object:
public ResponseEntity<?> createOrder(#Valid #RequestBody Cart cart, Errors errors) throws Exception {
In the createOrder method you can then query the Errors object and respond accordingly. This could mean sending a specific response from the controller or throwing an exception to be handled by your existing exception handling mechanism.

Related

JAX-RS / Jersey : Change response body from Jackson JSON errors

I have a rest end point using jersey, and I "drop" the payload in a DTO with a field which is ENUM type.
public enum AdminStatus {
ACTIVE, NONACTIVE;
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
public class AdminUpdateDTO {
#XmlElement(nillable = false, required = true)
private AdminStatus status;
public AdminStatus getStatus() {
return status;
}
public void setStatus(AdminStatus status) {
this.status = status;
}
#Override
public String toString() {
return "[status=" + this.getStatus() + "]";
}
}
When I give this field a value that not match with the existing ENUM values and make a request I get this:
Can not construct instance of com.utility.AdminStatus from String value 'ACTIVEE':
value not one of declared Enum instance names: [NONACTIVE, ACTIVE]
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream#2848cd9d; line: 1, column: 328] (through reference chain: com.dto.AdminUpdateDTO["status"])
Does Jersey has ability to handle this somehow, i mean, i don't want to get this as a response but some custom error response.
This is the default behavior of the Jackson provider exception mappers: to return the exception message as the response. If you want a different response, create your own mappers for JsonMappingException and JsonParseException, as mentioned here.
UPDATE
here are a couple examples
#Provider
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
#Override
public Response toResponse(JsonMappingException exception) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Boo..").type("text/plain").build();
}
}
#Provider
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
#Override
public Response toResponse(JsonParseException exception) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Boo...").type("text/plain").build();
}
}
Here the response will simply be Boo.... You need to make sure these mappers are registered. If you are using scanning, the class should be picked up from the #Provider annotation. Otherwise you will need to manually register, whichever way you register your resources and providers.

How to send/accept JSON using JerseyTest Framework

I am attempting to write a simple test class that emulates a RESTful Web Service creating a Customer via a POST method. The following fails at assertEquals, I receive a 400 Bad Request response. I cannot use debugger to observe stack trace. However the console tells me the following...
INFO: Started listener bound to [localhost:9998]
INFO: [HttpServer] Started.
public class SimpleTest extends JerseyTestNg.ContainerPerMethodTest {
public class Customer {
public Customer() {}
public Customer(String name, int id) {
this.name = name;
this.id = id;
}
#JsonProperty("name")
private String name;
#JsonProperty("id")
private int id;
}
#Override
protected Application configure() {
return new ResourceConfig(MyService.class);
}
#Path("hello")
public static class MyService {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public final Response createCustomer(Customer customer) {
System.out.println("Customer data: " + customer.toString());
return Response.ok("customer created").build();
}
}
#Test
private void test() {
String json = "{" +
"\"name\": \"bill\", " +
"\"id\": 4" +
"}";
final Response response = target("hello").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(json));
System.out.println(response.toString());
assertEquals(response.getStatus(), 200);
}
}
Instead of printing response.toString(), you can read the actual body using response.readEntity(String.class). What you will find in the body is an error message from Jackson
No suitable constructor found for type [simple type, class simple.SimpleTest$Customer]: can not instantiate from JSON object (need to add/enable type information?)
At first glance your Customer class looks ok; it has a default constructor. But the really problem is that Jackson cannot instantiate it because it is a non-static inner class. So to fix it, simply make the Customer class static.
public static class Customer {}
As a general rule, when working with JSON and Jackson with Jersey, often when you get a 400, it a a problem with Jackson, and Jackson is pretty good at spitting out a meaningful message that will help us debug.

JsonView does not work

I use the annotation #JsonView, but it doesn't work, here is my code and the return data, would you please help me to look where I am wrong.
My spring jar shows the edition of "spring-web-3.2.8.RELEASE.jar", and I just add this bean,I do not know whether it is useful or not, and I just use #JsonView directly in my code
<bean id = "jacksonMessageConverter" class = "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>
Here is the View class
public class View {
public interface Summary{};
}
This is the User entity class(with "set" "get" method omitted), it has several attribute which corresponding to the database, there is a "status" attribute needn't return in the json data.
public Class User{
#JsonView(View.Summary.class)
private Integer uid;
#JsonView(View.Summary.class)
private String first;
#JsonView(View.Summary.class)
private String last;
#JsonView(View.Summary.class)
private String email;
#JsonView(View.Summary.class)
private String password;
private Integer status;
}
I had the controller
#RequestMapping(value="/login", method=RequestMethod.POST)
#JsonView(View.Summary.class)
#ResponseBody
public Message login(String email, String password){
User user = userMapper.findUser(email,password);
Message message = new Message();
message.setUser(user);
return message;
}
}
and here is my Message class with the "set" "get" methods
public class Message {
private int box_hits;
private List<Box> boxes;
#JsonView(View.Summary.class)
private User user;
}
when I use the postman to test the url,it shows json data, obviously, it should not return with the attribute without #JsonView, what' wrong with my code?
{
"box_hits": 0,
"boxes": null,
"user": {
"uid": 1,
"first": "yuan",
"last": "kang",
"email": "123#qq.com",
"password": "123",
"status": 0
}
}
As described in the announcement blog post, this feature is only available as of Spring Framework 4.2. It won't work with Spring 3.2.8.

How to produce error response in json

Am writing a Restful Webservice Impl, where i consume and produce response in JSON format by annotating #Produces("application/json"). Am producing JSON response as well. Here am handling exception with a class where it has error code and error message. When am getting exception it is not produced in application/json format. I used ExceptionMapper to find a solution but it is `text/plain format.
snippet
public Class Confiuration{
#Path("getData")
#Consumes("application/json")
#Produces("application/json")
public JSONGetDataResponseVo getData(GetDataRequestVo datarequestVO)
throws FaultResponse {
JSONGetDataResponseVo response=new JSONGetDataResponseVo ();
DataServiceValidator.validateGetConfigurationAndDataRequest(datarequestVO);
....
....
}catch(ApplicationException applicationException){
throw new FaultResponse(applicationException,locale);
}
}
FaultResponseMapper
#Provider
public class FaultResponseMapper implements ExceptionMapper<FaultResponse> {
#Context
private HttpHeaders headers;
public Response toResponse(FaultResponse faultResponse) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(faultResponse).type(MediaType.APPLICATION_JSON).build();
}
}
Application Exception
public abstract class ApplicationException extends Exception{
private java.lang.String errorCode;
public ApplicationException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public ApplicationException(String message) {
super(message);
}
public java.lang.String getErrorCode() {
return this.errorCode;
}
public abstract String getLocaleMessage(Locale locale);
}
FaultResponse
public class FaultResponse extends WebApplicationException {
private String errorCode;
private String errorMessage;
private String localErrorMessage;
public FaultResponse(String errorCode, String errorMessage,
String localErrorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.localErrorMessage = localErrorMessage;
}
public FaultResponse(ApplicationException applicationException,
Locale locale) {
this.errorCode = applicationException.getErrorCode();
this.errorMessage = applicationException.getMessage();
if (locale != null
&& applicationException.getLocaleMessage(locale) != null) {
this.localErrorMessage = applicationException
.getLocaleMessage(locale);
} else {
this.localErrorMessage = applicationException.getMessage();
}
}
}
So here how can i produce my faultResponse in JSON format.
This has to do with the fact that you are returning an exception as a response. I would
Make an exception mapper for ApplicationException.
Refactor FaultResponse to not extend and exception. Just create it in the mapper.
In order to see the response, you will need to send a status other than No Content. You can't have a body in it. Send somethng like Bad Request.
You can just declare the resource method as throws ApplicationException. You don't need to catch it and rethrow.
I've made these changes, and it works fine.
UPDATE: with complete test
Added getters (required for marshalling) to FaultResponse and remove the exception extension
public class FaultResponse {
...
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public String getLocalErrorMessage() { return localErrorMessage; }
...
}
Created a Service for testing and ApplicationException implementation
public class ApplicationExceptionImpl extends ApplicationException {
public ApplicationExceptionImpl(){
this("400", "Bad Request");
}
public ApplicationExceptionImpl(String errorCode, String message) {
super(errorCode, message);
}
#Override
public String getLocaleMessage(Locale locale) {
return "Bad Request";
}
}
public class FaultService {
public void doSomething() throws ApplicationException {
throw new ApplicationExceptionImpl();
}
}
Resource class
#Path("fault")
public class FaultResource {
FaultService service = new FaultService();
#GET
public Response getException() throws ApplicationException {
service.doSomething();
return Response.ok("Cool").build();
}
}
ExceptionMapper
#Provider
public class ApplicationExceptionMapper implements ExceptionMapper<ApplicationException> {
#Override
public Response toResponse(ApplicationException exception) {
FaultResponse response = new FaultResponse(exception, Locale.ENGLISH);
return Response.status(Response.Status.BAD_REQUEST)
.entity(response).type(MediaType.APPLICATION_JSON).build();
}
}
ApplicationException class is left the same
curl -v http://localhost:8080/api/fault
{"errorCode":"400","errorMessage":"Bad Request","localErrorMessage":"Bad Request"}
If after this you are still not seeing JSON, it's possible you do not have a provider configured. If this is the case, please show your application configuration, along with your project dependencies.

Jersey unmarshal JSON: Last element null does not work

I am using Jersey to parse the following JSON:
{"response":{"status":"OK","campaigns":[{"id":12345,"state":"active","code":null}]}}
But I get the following error message:
java.lang.IllegalArgumentException: No more parsing elements.
If I switch the position of the fields code and state so that the resulting JSON looks like
{"response":{"status":"OK","campaigns":[{"id":12345,"code":null,"state":"active"}]}}
everything works fine. Also if I change the code-field in the first JSON to a non-null value like "code":"test", Jersey can parse this without any problems. I tried other more complex examples always getting the above mentioned error message when leaving the last field of any element of an array null.
I think I am doing something wrong, because I could not find any others having the similar problem. I already tried to implement a CustomJAXBContextResolver using other JSON notations like natural but nothing worked for me.
Any ideas?
Here are my binding classes:
#XmlRootElement
public class LoadEntityResponse {
public LoadEntityResponse() {
}
private Response response;
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
}
and
public class Response {
public Response() {
}
private String status;
private String error;
private String error_id;
private Campaign[] campaigns;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getError_id() {
return error_id;
}
public void setError_id(String error_id) {
this.error_id = error_id;
}
public Campaign[] getCampaigns() {
return campaigns;
}
public void setCampaigns(Campaign[] campaigns) {
this.campaigns = campaigns;
}
}
and finally
public class Campaign{
public Campaign() {
}
protected int id;
protected String code;
protected String state;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
Solved: Using JacksonJsonProvider now:
...
DefaultClientConfig config = new DefaultClientConfig();
config.getClasses().add(JacksonJsonProvider.class);
...
that´s all!
You can also use Jackson POJO support that comes with jersey-json but there is a need to do some configuration, see POJO support in Jersey User Guide.
Try using Genson http://code.google.com/p/genson/.
To enable it on client side use the following code:
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(GensonJsonConverter.class);
cli = Client.create(config);
EDIT: on server side there is no configuration needed, when the jar is in your classpath json support is automatically enabled.