Intercepting the #responsebody in spring mvc - spring

I have a Spring MVC web application with conroller like below :
#Controller
public class ActionRestController {
#RequestMapping(value = "/list", method = GET)
#ResponseBody
public List<Action> list(Action action, SearhCriteria searchCriteria) {
List<Action> ret = new ArrayList<Action>();
// Call a service method to get the records
// Copy the records into the list
// return the list of objects
return ret;
}
The Controller is invoked when the user does a search. There are several such controllers in the app, one for each searchable entity.
For reasons that I cannot explain very well, here, I cannot modify these controllers in anyway.
But now, I have requirement in the UI to display the search criteria and the no. of records and paging details, as well. This information is not returned by the controller. The JSON returned by the Controller contains just the list of records.
I have put up a different controller which will handle the request, gets and puts the extra info in the model and forwards the request to the existing controller like below :
#Controller
public class ActionExtendedController {
#RequestMapping(value = "/searchlist", method = GET)
#ResponseBody
public List<Action> list(Action action, SearhCriteria searchCriteria, Model model) {
model.addAttribute("searchParameters", searchCriteria);
return "forward:/list";
}
Upto this point, all is well.
What I want to do is intercept the request at a point where the List is returned from the controller, before it is converted to JSON, and return a map containing the list and the search parameters.
Now since the 'immutable' controller users ResponseBody the control goes to the JacksonMessageConverter amd the response goes out from there. I have already tried the following paths and they do not work.
Interceptor - By the time I get here, the response is already written out, so there is no way to change it.
Custom ObjectMapper for the JasksonMessageConverter - Will not work, since I do not have access to the model object inside the mapper, I only have access to the list returned by the controller.
Aspect #After pointcut for the controller - I think this technique will work, but I cannot get it to work. The advise does not fire and I am sure I am missing something in the configuration.
Is there a way to get Spring AOP to fire on a annotated controller, handler method or
can anyone suggest another method of intercepting the handler return value (along with the model) ?

How about a simple delegation to the base controller in your extended controller:
#Controller
public class ActionExtendedController {
#Autowired ActionRestController baseRestController;
#Autowired MappingJacksonJsonView mappingJacksonJsonView;
#RequestMapping(value = "/searchlist", method = GET)
public View list(Action action, SearhCriteria searchCriteria, Model model) {
List<Action> actions = baseRestController.list(action, searchCriteria, model);
model.addAttribute("actions", actions);
model.addAttribute("searchParameters", searchCriteria);
return mappingJacksonJsonView;
}
this way you are delegating to the original controller, but using this new controller for the view. Just register a mappingJacksonJsonView as a bean also which will serialize all model objects (searchcriteria and actions) into the json view. You need not even return a view but can also use #ResponseBody, with a type that can hold the responses and search criteria.

Why don't you change the return type to a Map? Like:
#Controller
public class ActionRestController {
#RequestMapping(value = "/list", method = GET)
#ResponseBody
public Map<String, Object> list(Action action, SearhCriteria searchCriteria) {
Map<String, Object> map = new HashMap<String, Object>();
List<Action> ret = new ArrayList<Action>();
// Call a service method to get the records
// Copy the records into the list
// return the list of objects
map.put("searchResult",ret);
map.put("searchCriteria", searchCriteria);
return map;
}

Related

Spring common method between #RequestMappings

In a Servlet, you can include an #Override service method which gets called before the doGet or doPost, is there a way to achieve the same in a Spring #Controller?
Or more precisely, in each method in the Controller, I need to make sure an Entity (in this case, a Product) exists and redirect otherwise, like so, so how would one achieve that in Spring? Note that I also need the Product available in each Method.
#Controller
#RequestMapping("/product/{prod_id}/attribute")
public class AttributeController {
#Autowired
private AttributeService attributeService;
#RequestMapping(value = "/add", method = RequestMethod.GET)
public String add(Model model, #PathVariable Long prod_id) {
Product product = attributeService.getProduct(prod_id);
if (product == null) {
return "products/products";
}
model.addAttribute("product", product);
model.addAttribute("attribute", new Attribute());
return "products/attribute_add";
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String save(Model model, #PathVariable Long prod_id, #Valid Attribute attribute, BindingResult result) {
Product product = attributeService.getProduct(prod_id);
if (product == null) {
return "products/products";
}
// ...
}
// ...
}
This can be done with HandlerInterceptor. All you need to do is to extend HandlerInterceptorAdapter#preHandle and then register your interceptor through WebMvcConfigurer#addInterceptors. You can choose to use interceptor with all your mappings or with some specific mappers through InterceptorRegistration object with is returned by InterceptorRegistry#addInterceptor method.
By the way, HandlerInterceptors are useful to do some utility operations with requests and responses in general, like logging, adding headers, authentication, etc. For business-related operations I would recommend to use ControllerAdvice with custom business-oriented exceptions. In this case it would be a method which retrieves Product from database and throws custom exception if not found.

How to call one controller to another controller URL in Spring MVC?

Hi I am new to Spring MVC ,I want to call method from one controller to another controller ,how can I do that .please check my code below
#Controller
#RequestMapping(value="/getUser")
#ResponseBody
public User getUser()
{
User u = new User();
//Here my dao method is activated and I wil get some userobject
return u;
}
#Controller
#RequestMapping(value="/updatePSWD")
#ResponseBody
public String updatePswd()
{
here I want to call above controller method and
I want to update that user password here.
how can I do that
return "";
}
any one help me .
Can do like this:
#Autowired
private MyOtherController otherController;
#RequestMapping(value = "/...", method = ...)
#ResponseBody
public String post(#PathVariable String userId, HttpServletRequest request) {
return otherController.post(userId, request);
}
You never have to put business logic into the controller, and less business logic related with database, the transactionals class/methods should be in the service layer. But if you need to redirect to another controller method use redirect
#RequestMapping(value="/updatePSWD")
#ResponseBody
public String updatePswd()
{
return "redirect:/getUser.do";
}
A controller class is a Java class like any other. Although Spring does clever magic for you, using reflection to examine the annotations, your code can call methods just as normal Java code:
public String updatePasswd()
{
User u = getUser();
// manipulate u here
return u;
}
You should place method getUser in a service (example UserService class) .
In the getUser controller, you call method getUser in the Service to get the User
Similarly, in the updatePswd controller, you call method getUser in the Service ,too
Here no need to add #reponseBody annotation as your redirecting to another controller
Your code will look like
#Controller
class ControlloerClass{
#RequestMapping(value="/getUser",method = RequestMethod.GET)
#ResponseBody
public User getUser(){
User u = new User();
//Here my dao method is activated and I wil get some userobject
return u;
}
#RequestMapping(value="/updatePSWD",method = RequestMethod.GET)
public String updatePswd(){
//update your user password
return "redirect:/getUser";
}
}

Spring MVC, Controllers design

I am building a web application in Spring MVC with combination of Spring Security. My question regards to inner design of application. To be more specific - how to set up controllers. I got inspired a lot by Pet Clinic example where there is one controller per domain object (Owner controller, Pet controller, Vet Controller and so on).
I would like to introduce an admin backend interface to my application. This would mean to create admin - specific methods and #RequestMappings in each controller. Request mapping paths are secured by intercept-url pattern so I do not have to care where they are. However I find this solution little bit inelegant.
On pet clinics example would it look like:
#Controller
#SessionAttributes(types = Owner.class)
public class OwnerController {
private final ClinicService clinicService;
// Front end method
#RequestMapping(value = "/owners/find", method = RequestMethod.GET)
public String initFindForm(Map<String, Object> model) {
model.put("owner", new Owner());
return "owners/findOwners";
}
// Admin method
#RequestMapping(value = "/admin/owners/find", method = RequestMethod.GET)
public String initFindForm(Map<String, Object> model) {
model.put("owner", new Owner());
//Admin view
return "admin/owners/findOwners";
}
}
Other choice is to have one controller for each #RequestMapping (or per action)
#Controller
#RequestMapping(value = "/admin", method = RequestMethod.GET)
public class AdminController {
private final ClinicService clinicService;
// Admin method
#RequestMapping(value = "/owners/find", method = RequestMethod.GET)
public String initFindForm(Map<String, Object> model) {
model.put("owner", new Owner());
//Admin specific view
return "admin/owners/findOwners";
}
}
This would in my opinion lead to really robust controllers with many methods.
Third option would be to have some kind of mix of those.
#Controller
#SessionAttributes(types = Owner.class)
public class AdminOwnerController {
private final ClinicService clinicService;
// Admin method
#RequestMapping(value = "/admin/owners/find", method = RequestMethod.GET)
public String initFindForm(Map<String, Object> model) {
model.put("owner", new Owner());
//Admin view
return "admin/owners/findOwners";
}
}
My question is what is a standard approach for that?
Usually I use a hybrid approach of AdminOwnerController, in which I end up having approximately 5-10 methods max per Controller.
If you end up having 1-2 methods per controller. I would consider grouping them together based on the admin domain.

How to map Multiple controllers in Spring MVC

I have two controllers in my Application; one is userController, where I have add, delete and update methods; the other one is studentController, where I also have add, delete and update methods.
All the mappings are same in my methods using #RequestMapping annotation in both controllers. I have one confusion: if we are passing the same action from the JSP, then how will the Dispatcher find the corresponding controller? If anybody could describe this using example will be appreciated.
You have to set a #RequestMapping annotation at the class level the value of that annotation will be the prefix of all requests coming to that controller,
for example:
you can have a user controller
#Controller
#RequestMapping("user")
public class UserController {
#RequestMapping("edit")
public ModelAndView edit(#RequestParam(value = "id", required = false) Long id, Map<String, Object> model) {
...
}
}
and a student controller
#Controller
#RequestMapping("student")
public class StudentController {
#RequestMapping("edit")
public ModelAndView edit(#RequestParam(value = "id", required = false) Long id, Map<String, Object> model) {
...
}
}
Both controller have the same method, with same request mapping but you can access them via following uris:
yourserver/user/edit
yourserver/student/edit
hth
We can have any number of controllers, the URL mapping will decide which controller to call..
Please refer here for detailed Spring MVC multiple Controller example

Force Initialization of #ModelAttributes in Spring MVC 3.1

I am writing a wizard-like controller that handles the management of a single bean across multiple views. I use #SessionAttributes to store the bean, and SessionStatus.setComplete() to terminate the session in the final call. However, if the user abandons the wizard and goes to another part of the application, I need to force Spring to re-create the #ModelAttribute when they return. For example:
#Controller
#SessionAttributes("commandBean")
#RequestMapping(value = "/order")
public class OrderController
{
#RequestMapping("/*", method=RequestMethod.GET)
public String getCustomerForm(#ModelAttribute("commandBean") Order commandBean)
{
return "customerForm";
}
#RequestMapping("/*", method=RequestMethod.GET)
public String saveCustomer(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the customer data ];
return "redirect:payment";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String getPaymentForm(#ModelAttribute("commandBean") Order commandBean)
{
return "paymentForm";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String savePayment(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the payment data ];
return "redirect:confirmation";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String getConfirmationForm(#ModelAttribute("commandBean") Order commandBean)
{
return "confirmationForm";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String saveOrder(#ModelAttribute("commandBean") Order commandBean, BindingResult result, SessionStatus status)
{
[ Save the payment data ];
status.setComplete();
return "redirect:/order";
}
#ModelAttribute("commandBean")
public Order getOrder()
{
return new Order();
}
}
If a user makes a request to the application that would trigger the "getCustomerForm" method (i.e., http://mysite.com/order), and there's already a "commandBean" session attribute, then "getOrder" is not called. I need to make sure that a new Order object is created in this circumstance. Do I just have to repopulate it manually in getCustomerForm?
Thoughts? Please let me know if I'm not making myself clear.
Yes, sounds like you may have to repopulate it manually in getCustomerForm - if an attribute is part of the #SessionAttributes and present in the session, then like you said #ModelAttribute method is not called on it.
An alternative may be to define a new controller with only getCustomerForm method along with the #ModelAttribute method but without the #SessionAttributes on the type so that you can guarantee that #ModelAttribute method is called, and then continue with the existing #RequestMapped methods in the existing controller.

Resources