Validating Json during model binding in spring MVC 3.1 - json

I'm attempting to bind json to the model with #RequestBody.
We are validating our model using JSR303.
Here is the method signature
#RequestMapping(value = "/editCampaign.do", method = { RequestMethod.POST })
public ModelAndView editCampaign(#RequestBody #Valid Campaign campaign,
HttpServletRequest request)
If a piece of required information is missing a MethodArgumentNotValidException is thrown (as I read in the docs). I really want to be able to return this information back to the view so I can show the user that they've not filled out a required field. Once the exception is thrown, it seems as though it's too late. Obviously, I don't have a bindingresult to inspect.
Am I incorrectly using the framework? How do I set up this scenario correctly?

First Of all I recommend you to return String instead ModelAndView especially in Spring 3.1.
If you want to catch Exception from #ResponseBody annotated method, I recommend you to use the following:
1) Catch the exception with #ExceptionHandler annotation:
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleMyException(MethodArgumentNotValidException exception) {
return "redirect:errorMessage?error="+exception.getMessage());
}
and then Redirect to view annotated with #ResponseBody argument
#RequestMapping(value="/errorMessage", method=RequestMethod.GET)
#Responsebody
public String handleMyExceptionOnRedirect(#RequestParamter("error") String error) {
return error;
}

Related

Is it possible to get an InvalidFormatException not as a HttpMessageNotReadableException in Spring when processing JSON?

I have a simple RestController:
#RestController
public class MailboxController{
#RequestMapping(value = "/mailbox/{id}", method = RequestMethod.PUT)
public void createMailbox(#PathVariable #NotNull String mailboxID, #Validated #RequestBody Mailbox mailbox){
//do something with Mailbox here
}
}
The Mailbox class looks as follows:
#Validated
public class Mailbox{
#JsonProperty("email")
#EmailValidator //some validation of EMail String
public String email;
#JsonProperty("type")
public Type type; //Type is an enum
}
If I post a valid JSON-String where the value of type is not listed in the enumeration then I get a 400 (Bad Request) error. Internally a HttpMessageNotReadableException with a InvalidFormatException gets thrown. If I post a JSON-String where the email doesn't get accepted by the EmailValidator I get a 400 (Bad Request) error as well (from a MethodArgumentNotValidException). In both cases I would expect a 422 since the JSON is well formatted but the content is semantically wrong.
Either way I try to handle both cases similary and wonder if there is a way to redirect the InvalidFormatException in a way that I get MethodArgumentNotValidException when the given type is not part of the Type enumeration.
Currently I have a ResponseEntityExceptionHandler with one method that catchest the HttpMessageNotReadableException and builds the ResponseEntity depending on the causing exception and a method that handles the MethodArgumentNotValidException. I would prefer it if the InvalidFormatException could be handled in the method where I handle the MethodArgumentNotValidException as well.

Json Deserializer not detected by Spring

I have a Spring Rest Controller and a "command"/DTO object for the POST method on the controller. I also wrote a serializer / deserializer for one of the fields "due" - which is a Calendar object.
Since Jackson2 dependencies are defined in pom.xml, I expect Spring to detect my deserializer and use it to convert a String input to java.util.Calendar. However, I get a "no matching editors or conversion strategy found" exception. My serializer is working ... only the deserializer won't work!
Rest Controller (TaskController.java)
#Controller
public class TasksController {
...
#RequestMapping(value = "/tasks", method = RequestMethod.POST)
public #ResponseBody Task createTask(#Valid TasksCommand tasksCommand){
Task task = new Task();
task.setName(tasksCommand.getName());
task.setDue(tasksCommand.getDue());
task.setCategory(tasksCommand.getCategory());
return task;
}
}
The command / dto object:
public class TasksCommand {
#NotBlank
#NotNull
private String name;
#JsonDeserialize(using = CalendarDeserializer.class)
#JsonSerialize(using = CalendarSerializer.class)
private Calendar due;
private String category;
... getters & setters ...
}
Serializer for Calendar - for my custom date format
public class CalendarSerializer extends JsonSerializer<Calendar>{
#Override
public void serialize(Calendar calendar, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss Z"); // Z - RFC 822 time zone
jgen.writeString(simpleDateFormat.format(calendar.getTime()));
}
}
Deserializer for Calendar
public class CalendarDeserializer extends JsonDeserializer<Calendar> {
private static Logger logger = Logger.getLogger(CalendarDeserializer.class);
#Override
public Calendar deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String dateStr = jsonParser.getText();
logger.info("deserializing date:" + dateStr);
return Calendar.getInstance();
}
}
I have Jackson2 dependencies specified in Maven. When doing a GET (code not shown here), the serializer gets invoked correctly, and I see a JSON output of the "Task" object.
However, when I make an HTTP POST as below
curl -X POST -d name=task1 -d category=weekly -d due=01/01/2013 http://localhost:8080/tasks
the deserializer is never detected and I get an exception
Failed to convert property value of type java.lang.String to required type java.util.Calendar for property due; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Calendar] for property due: no matching editors or conversion strategy found
Since the serializer is getting detected, I don't understand why the deserializer is not detected and invoked correctly. From what I have read, having Jackson2 library on classpath will be detected by Spring and it would automatically add MappingJackson2HttpMessageConverter to the list of message converters. So this code should detect the string in HTTP POST and convert it to java.util.Calendar using the Deserializer. What am I missing?
Figured this one out.
When making an HTTP POST using Curl as shown in the description (with the -d flag), the POST comes in with content type "application/x-www-form-urlencoded" (obvious in hindsight, since it's treated as a web form data). This triggers the Spring FormHttpMessageConverter message converter and MappingJackson2HttpMessageConverter converter is never triggered.
However, the serializer and deserializer are annotated with Jackson annotations and only MappingJackson2HttpMessageConverter knows how to handle that. This is what causes the exception described in the problem.
The solution is to pass in JSON data in the curl command:
curl -X POST -H "Content-Type: application/json" -d '{"name":"test1","due":"1/1/2013","category":"weekly"}' http://localhost:8080/tasks
Also, since the JSON input comes in the body of the HTTP POST call, we need to use the #RequestBody annotation in the Controller method signature.
#RequestMapping(value = "/tasks", method = RequestMethod.POST)
public #ResponseBody Task createTask(#RequestBody #Valid TasksCommand tasksCommand){
Task task = new Task();
task.setName(tasksCommand.getName());
task.setDue(tasksCommand.getDue());
task.setCategory(tasksCommand.getCategory());
return task;
}
Now the deserializer gets triggered and the input due date is converted from String to Calendar object correctly.

Unsupported Media Type Error in AJAX-Spring

I am trying to pass POST data from my jsp with jquery-ajax to my Spring-MVC controller function. The data is passed fine and I can print the JSON data when I use a String object to receive the RequestBody. But when I employ a DTO which has a List variable declared with its own objects the controller returns a '415 Unsupported Media Type Error' with the following statement,
The server refused this request because the request entity is in a format not supported by the requested resource for the requested method.
below given is the DTO class
public class anyClassDTO{
private String name;
private List<anyClassDTO> subList = new ArrayList<anyClassDTO>();
//getters and setters here
}
Now, here is the controller function
#RequestMapping(headers ={"Accept=application/json"}, value = "urlFromJsp", method = RequestMethod.POST)
public #ResponseBody
String addData (HttpServletRequest request,
#RequestBody List<anyClassDTO> dtoObject,
Model model)
{
return "{\"value\":\"true\"}";
}
Is it not possible for a list of objects to be received from the jsp page to a controller via AJAX?
Here is a set of sample data being passed from the jsp
[{"name":"module1","subList":[{"name":"chapter1","subList":[{"name":"subchapter1","subList":null}]}]},{"name":"module2","subList":[{"name":"chapter1","subList":[{"name":"subchapter1","subList":null}]}]}]
Make sure your AJAX request sets the request's Content-Type to application/json.
Spring typically uses a MappingJacksonHttpMessageConverter to convert the request body when you specify #RequestBody. This HttpMessageConverter only supports application/*+json type content types, so you have to make sure your request contains it.
Well, we could make it work as it is by adding a little more detail. Instead of receiving the #ResponseBody as a List object I created another DTO which holds a List object of the original DTO. So the second DTO is basically a dummy which receives the data from AJAX as a single object.
Like I have said in the question I have a DTO as follows
public class AnyClassDTO{
private String name;
private List<anyClassDTO> subList = new ArrayList<anyClassDTO>();
//getters and setters here
}
I created another DTO which holds a List of the above DTO
public class DummyDTO{
private List<AnyClassDTO> dummyObj;
//getters and setters here
}
Then in the controller I changed the function to
#RequestMapping(headers ={"Accept=application/json"}, value = "urlFromJsp", method = RequestMethod.POST)
public #ResponseBody
String addData (HttpServletRequest request,
#RequestBody DummyDTO dummyDTOObj,
Model model)
{
return "{\"value\":\"true\"}";
}
Earlier if I was sending a list directly from AJAX, now I am sending a stringified litteral with a variable which holds the whole data.
And it works like a charm!

Issues with JSON processing using JAXBElement under Jersey 2.2 with MOXy

I extended the jersey-examples-moxy code to use an XML schema definition instead of the JAXB annotated beans. The xjc compiled XML schema produces XML and JSON encodings identical to the original example.
I followed the jersey instructions and used the ObjectFactory to generate the JAXBElement Customer object representation within CustomerResource.java. I also modified the client as described. I also incorporated the fix described in PUT issues with JSON processing using JAXB under Jersey 2.2 with MOXy
The MediaType.APPLICATION_XML functions perfectly, and MediaType.APPLICATION_JSON works for GETs, but the client is failing to marshall JSON on a PUT with "MessageBodyWriter not found". The following exception is thrown:
testJsonCustomer(org.glassfish.jersey.examples.jaxbmoxy.MoxyAppTest) Time elapsed: 0.113 sec <<< ERROR!
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=application/json, type=class javax.xml.bind.JAXBElement, genericType=class javax.xml.bind.JAXBElement.
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:191)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:139)
at org.glassfish.jersey.filter.LoggingFilter.aroundWriteTo(LoggingFilter.java:268)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:139)
at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1005)
at org.glassfish.jersey.client.ClientRequest.writeEntity(ClientRequest.java:430)
at org.glassfish.jersey.client.HttpUrlConnector._apply(HttpUrlConnector.java:290)
Here is how I modified CustomerResource.java:
private static ObjectFactory factory = new ObjectFactory();
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public JAXBElement<Customer> getCustomer() {
return factory.createCustomer(customer);
}
#PUT
#Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public JAXBElement<Customer> setCustomer(Customer c) {
customer = c;
return factory.createCustomer(customer);
}
Here is how I am making the PUT request (same as for the functioning XML):
#Override
protected void configureClient(ClientConfig clientConfig) {
clientConfig.register(new MoxyXmlFeature());
}
#Test
public void testJsonCustomer() throws Exception {
ObjectFactory factory = new ObjectFactory();
final WebTarget webTarget = target().path("customer");
// Target customer entity with GET and verify inital customer name.
Customer customer = webTarget.request(MediaType.APPLICATION_JSON).get(Customer.class);
assertEquals("Tom Dooley", customer.getPersonalInfo().getName());
// Update customer name with PUT and verify operation successful.
customer.getPersonalInfo().setName("Bobby Boogie");
Response response = webTarget.request(MediaType.APPLICATION_JSON).put(Entity.json(factory.createCustomer(customer)));
assertEquals(200, response.getStatus());
// Target customer entity with GET and verify name updated.
Customer updatedCustomer = webTarget.request(MediaType.APPLICATION_JSON).get(Customer.class);
assertEquals(customer.getPersonalInfo().getName(), updatedCustomer.getPersonalInfo().getName());
}
Thank you for your help!
The issue you're facing is on this line:
Response response = webTarget.request(MediaType.APPLICATION_JSON).put(Entity.json(factory.createCustomer(customer)));
Basically you're passing JAXBElement to Entity#json method but the runtime doesn't have information about the generic type, you need to provide it. That's what GenericEntity<T> class is for:
webTarget
.request(MediaType.APPLICATION_JSON)
.put(Entity.json(new GenericEntity<JAXBElement<Customer>>(factory.createCustomer(customer)) {}));

Using Spring's #RequestBody and reading HttpServletRequest.getInputStream() afterwards

I'm mapping my request's JSON POST data into an object using Spring's #RequestBody annotation and MappingJacksonHttpMessageConverter. However after that I'd like to read the data in String form to do some additional authentication. But when the marshalling has happened, the InputStream in HttpServletRequest is empty. Once I remove the #RequestBody parameter from the method the reading of POST data into a String works as expected.
Do I have to compromise by giving up the #RequestBody and doing the binding somehow manually or is there a more elegant solution?
So, basically you need to compute a hash of the request body. The elegant way to do it is to apply a decorator to the InputStream.
For example, inside a handler method (in this case you can't use #RequestBody and need to create HttpMessageConverter manually):
#RequestMapping(...)
public void handle(HttpServletRequest request) throws IOException {
final HashingInputStreamDecorator d =
new HashingInputStreamDecorator(request.getInputStream(), secretKey);
HttpServletRequest wrapper = new HttpServletRequestWrapper(request) {
#Override
public ServletInputStream getInputStream() throws IOException {
return d;
}
};
HttpMessageConverter conv = ...;
Foo requestBody = (Foo) conv.read(Foo.class, new ServletServerHttpRequest(wrapper));
String hash = d.getHash();
...
}
where hash is computed incrementally in overriden read methods of HashingInputStreamDecorator.
You can also use #RequestBody if you create a Filter to apply the decorator. In this case decorator can pass the computed hash to the handler method as a request attribute. However, you need to map this filter carefully to apply it only to the requests to specific handler method.
In your urlMapping bean you can declare list of additional interceptors:
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<bean class="org.foo.MyAuthInterceptor"/>
</list>
</property>
</bean>
Those interceptors have access to HttpServletRequest, though if you read from the stream the chances are that parameter mapper won't be able to read it.
public class AuthInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav) {
...
}
}
If I understand this correctly, one common way used with JAX-RS (which is somewhat similar to Spring MVC with respect to binding requests) is to first "bind" into some intermediate raw type (usually byte[], but String also works), and manually bind from that to object, using underlying data binder (Jackson). I often do this to be able to fully customize error handling of data binding.