Handle HTML and non-HTML representations in controller with HttpMessageConverters - spring

I have a Spring MVC controller for a RESTful resource (ServiceDirectoryController), which I want to serve both HTML (web page) and non HTML (web service) representations. I want to use view names and JSP to generate the HTML representation, but HttpMessageConverters for the other representations. How do I do that?
I've implemented the request handling method for the non-HTML representations. My internal (domain-model) representation is ThingNames, for which I have suitable message converters, so the request handling method is simply:
#RequestMapping(value = { "/directory/" }, method = RequestMethod.GET)
#ResponseBody
public ThingNames retrieveThings() {
...
}
Now I want to add the request handling method for the JSP page. So I need a method that indicates the view name and provides a suitable model. So, the obvious thing to do is this:
#RequestMapping(value = { "/directory/" }, method = RequestMethod.GET)
public String retrieveThings(Map< String, Object > model) {
...
}
with the view name as the return value.
But will that work? Is Spring clever enough to work out that the HTML representation should be handled by the second method, and use the first method for all other representations? And if not, how do I go about handling both JSP/HTML and non JSP/HTML representations?

According to a Spring blog post by Paul Chapman, my controller will work as written if I set up a PPA Content Negotiation Strategy. That is, if I configure Spring to consider clues about the wanted content type in the following descending order of preference:
Path extension (.htm, .tsv, etc.)
Request parameter (?format=htm, ?format=xls)
Accept header (proper HTTP content negotiation, which sadly works poorly with HTML browsers).
What is more, the Spring provided ContentNegotiationManagerFactoryBean operates in just that order.
In addition, you must annotate the handler methods in the controller so Spring knows that those methods should handle HTML content, using the consumes and produces values of the #RequestMapping annotation. That is, instead of simply writing
#RequestMapping(value = { "/directory/" }, method = RequestMethod.GET)
#ResponseBody
public String retrieveThings(Map< String, Object > model) {
...
}
write
#RequestMapping(value = { "/directory/" }, method = RequestMethod.GET,
produces = MediaType.TEXT_HTML_VALUE)
#ResponseBody
public String retrieveThings(Map< String, Object > model) {
...
}

Related

#ModelAttribute vs #RequestBody, #ResponseBody

#ModelAttribute
RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method =
RequestMethod.POST)
public String processSubmit(#ModelAttribute Pet pet) { }
http://.../?name=Something&age=100
public String doSomething(#ModelAttribute User user) { }
#RequestBody
#RequestMapping(value = "/user/savecontact", method = RequestMethod.POST
public String saveContact(#RequestBody Contact contact){ }
{ "name": "Something", "age": "100" } in request body
public String doSomething(#RequestBodyUser user) { }
#ModelAttribute will take a query string. so, all the data are being pass to the server through the url
#RequestBody, all the data will be pass to the server through a full JSON body
Now which one is the best approach ???
If both are for same purpose to bind to the bean..which one is the best practice or widely used as standard practice?
Both handles multi-part file and does it both have equivalent options with one another ?
https://javabeat.net/spring-multipart-file-upload/
How do i upload/stream large images using Spring 3.2 spring-mvc in a restful way
Does any one of them has lesser capabilities then the other one? Like length limitations, method limitations. Drawbacks
Which one is more secured in terms of security ?
As the javadoc suggests, it's the usage that sets them apart, i.e., use #ModelAttribute if you want to bind the object back to the web view, if this is not needed, use #RequestBody
#RequestBody
Usecases : Restful controllers (ex: produce and consume json/xml, processing direct document download requests, searching for stuff, ajax requests )
As the name suggests the if a method argument is annotated with #RequestBody annotation Spring converts the HTTP request body to the Java type of the method argument.
Is only allowed on method parameters (#Target(value={ PARAMETER}))
The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request.
works for Post and not Get method.
#ModelAttribute
Usecases : web app controllers (ex: binding request query parameters, populating web views with options and defaults)
Uses data binders & ConversionService
Is allowed on methods and method params(#Target(value={METHOD, PARAMETER}))
Useful when dealing with model attributes for adding and retrieving model attributes to and from the Srping’s Model object
When used on METHODS, those methods are invoked before the controller methods annotated with #RequestMapping are invoked
binds a method PARAMETER or method return value to a named model attribute & the bound named model attributes are exposed to a web view
binds request query parameters to bean
For more information on Data Binding, and Type Conversion refer: https://docs.spring.io/spring/docs/5.1.x/spring-framework-reference/core.html#validation

how to apply post method to all request mappings in spring

How i can access to request POST data from different url-s to one controller method, for example I have /countries & /countries/{id} URL, It works very good with first one, because my code is
#RequestMapping(value = {"/{id}", "/{id}/"}, method = RequestMethod.GET)
public String getCountry(#PathVariable(value = "id", required = true) int id,ModelMap model) {
}
#RequestMapping(method = RequestMethod.POST)
public String deleteCountry(ModelMap model,HttpServletRequest request) {
}
And when I try to request POST data from second url I'm getting
HTTP Status 405 - Request method 'POST' not supported
Which is expectable because I haven't POST method with this mapping, but if I will be made one method for one mapping my code will be too ugly ant terrible, what solution I can use?
Hum why not add the "array" of value to your second method as well ?
#RequestMapping(value = {"", "/{id}"},method = RequestMethod.POST)
public String deleteCountry(ModelMap model,
HttpServletRequest request) {
Btw using POST verb to call an action that looks like it will delete the resource seems wrong, it should be a DELETE verb used
EDIT
But in reality, you should be creating 2 methods, as both those method are not supposed to do the same thing.
POST /countries should be creating country resources
POST /countries/{id} should be doing something else.
For an update prefer PUT /countries/{id}
And for a delete, prefer DELETE /countries/{id}
There is one way to have separate handler interceptors for different controllers.
Refer this link for details.
bind Spring HandlerInterceptor only to one controller
But I feel, it may be good you can create a common method to share business logic for this.
As Interceptor comes with proxy class for your controller which can be avoided unless you have complex logic.

Conditionally returning both JSON and (HTML) templates from #RestController in Spring Boot

Most of the similar questions seem to have the opposite problem I’m having.
I’m building a Spring Boot-based webapp using #RestController. The JSON responses work well, but now I want to support returning HTML via templates (Thymeleaf, specifically). All the examples show building methods like this:
#RequestMapping(method = RequestMethod.GET)
String index()
{
return "index";
}
And that works fine, so long as the class it’s in is annotated with #Controller. If I annotate with #RestController, I get the literal string "index" back. This makes sense, since #RestController implies #ResponseBody.
I have a few questions about this in general…
Is the right thing to do to use #Controller and explicit #ResponseBody annotations on the methods designed to return JSON?
I worry that my Controller classes will grow quite large, as I’ll have two implementations for most of the GET methods (one to return the HATEOAS JSON, one to return HTML with a lot more stuff in the model). Are there recommended practices for factoring this?
Advice is appreciated. Thanks!
Is the right thing to do to use #Controller and explicit #ResponseBody annotations on the methods designed to return JSON?
It is as long as your controllers are small and contain only few methods.
I worry that my Controller classes will grow quite large, as I’ll have two implementations for most of the GET methods (one to return the HATEOAS JSON, one to return HTML with a lot more stuff in the model). Are there recommended practices for factoring this?
If they grow and become hard to read split into one #Controller returning HTML pages and #RestController returning JSON.
To summarise, focus on readability. Technically both approaches are correct.
#RestController
public class SampleController {
#GetMapping(value = "/data/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public CustomClass get(#PathVariable String id) {
return newsService.getById(id);
}
#GetMapping(value = "/data/{id}", produces = MediaType.TEXT_HTML_VALUE)
public String getHTML(#PathVariable String id) {
return "HTML";
}
}
Instead of a String you can return a View or a ModelAndView:
#RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
ModelAndView index()
{
return new ModelAndView("index");
}
That allows you to return HTML from controllers annotated with #RestController.

Spring MVC REST - Block access to Web Methods

I have this method in my Controller that returns a json string to the view
#RequestMapping(value = "/getDevs", method=RequestMethod.GET)
public#ResponseBody String getDevs() throws JsonProcessingException,RemoteException,ServiceException{
ObjectMapper om = new ObjectMapper();
return om.writeValueAsString(WSCall.getDevelopers());
}
I call the method URL using ajax. Everything works fine except I can obtain the json if I put the URL directly in the browser. Is there a way to block this?
I agree with the comments above, that it is not relevant from a security standpoint, but you could probably make use of the X-Requested-With: XMLHttpRequest header that is most likely set for your AJAX requests. I can at least confirm it for jQuery, which might be on your tool stack.
So you could add the headers parameter to your #RequestMapping annotation:
#RequestMapping(value = "/getDevs", method=RequestMethod.GET, headers = { "X-Requested-With=XMLHttpRequest" })
public#ResponseBody String getDevs() throws JsonProcessingException,RemoteException,ServiceException{
[...]
}

Spring MVC + JSON in #ResponseBody + inheritance

I have a class hierarchy. On the top of it there is a an abstract AnswerUnit class. There are two inheriting classes: OpenQuestionAnswer and MultipleChoiceQuestionAnswer.
I have a .jsp form that sends data (object serialized to JSON) to server with AJAX request and a method in controller to handle it.
#RequestMapping(value = "/test", method = RequestMethod.POST)
public #ResponseBody
String testPostMethod(#RequestBody
OpenQuestionAnswer answer) {
return "home";
}
I would like to be able to take "AnswerUnit answer" as the argument (abstract type instead of concrete type, so I could handle request from different views with one method). When I try to do it there is a problem - server respose is
400 BAD REQUEST he request sent by the client was syntactically incorrect.
I think that the reason is that Spring (Jackson?) isn't able to find out which concrete class he should create and use.
On the client side I know what type of class I send to server.
What is the proper way to tell server which concrete class should be created and filled with my request?
I guess I'm late with response, but anyway:)
http://wiki.fasterxml.com/JacksonAnnotations
You can have this using Jackson Polymorphic type handling
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes(value = {
#JsonSubTypes.Type(name = "answer", value = OpenQuestionAnswer.class),
#JsonSubTypes.Type(name = "multiple", value = MultipleChoiceQuestionAnswer.class)
})
public class AnswerUnit
...
But you would need to add "type" field to your client JSON.

Resources