Could Spring MVC call #ModelAttribute after #RequestMapping? - java

I have a controller like this:
#Controller
public class HomeController {
#RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(#RequestParam("user") User user, ModelMap model){
SaveUserToDatabase(user);
return "index";
}
#ModelAttribute("user")
String getUser() {
return LoadCurrentUserFromDataBase();
}
}
In short, my views would render user in almost every actions in HomeController,
but I don't want to code:
model.addAttribute("user", LoadCurrentUserFromDataBase())
in every actions, instead I'm seeking a way like #ModelAttribute to expose user to all my views.
However, according to the docs, #ModelAttribute methods in a controller are invoked before #RequestMapping methods, within the same controller.
As to my code, getUser is called before update, but i'd like to get the updated user.
Is there a way to expose the user attribute after actions without explicitly call model.addAttribute in every actions?

Each time you do a POST, make a redirection. That way, your POST method will only be responsible for updating the data, and the updated data will be loaded by the target controller.
So in this case, the update() method would redirect to another controller which would call the getUser() method before its GET method.

The Post / redirect / GET solution is valid if it works for you.
However, I had a similar situation where I had model attributes that needed to be written by all my controllers, after request processing (tracking information mostly). What I did was to register a custom interface (e.g. AdditionalModelDataSupplier), which I apply to all controllers that need to provide additional data. The interface would have a method like this:
void provideAdditionalData(Model model, HttpServletRequest request);
Now, I wrote an interceptor that in the postHandle method checks the Controller bean for this interface and calls this method:
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws Exception {
AdditionalModelDataSupplier modelDataSupplier = findAdditionalModelDataSupplier(handler);
if (modelDataSupplier != null) {
final ModelMap modelMap = modelAndView.getModelMap();
final Model targetModel;
if (modelMap instanceof Model) {
targetModel = (Model) modelMap;
} else {
// the modelmap doesn't implement model, so we need to provide a wrapper view
targetModel = new ForwardingModel(modelMap);
}
modelDataSupplier.provideAdditionalData(targetModel, request);
}
}
#Nullable
private static AdditionalModelDataSupplier findAdditionalModelDataSupplier(final Object handler) {
if (handler instanceof AdditionalModelDataSupplier) {
return (AdditionalModelDataSupplier) handler;
}
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Object bean = handlerMethod.getBean();
if (bean instanceof AdditionalModelDataSupplier) {
return (AdditionalModelDataSupplier) bean;
}
}
return null;
}
(the ForwardingModel class mentioned above is trivial to create, it just delegates everything to the ModelMap)

Related

Get original mapping value inside Spring controller method

Since I'm using the CQRS pattern, I'm trying to create a single controller method that accepts every POST call with a command in its request body and send it.
I'm almost there, but I can't get the path variables.
I created a custom HandlerMapping
#Bean
public HandlerMapping requestMappingHandlerMapping() throws NoSuchMethodException {
for (final UrlEnum urlEnumItem : UrlEnum.values()) {
requestMappingHandlerMapping.registerMapping(new RequestMappingInfo(urlEnumItem.getCommandName(),
new PatternsRequestCondition(urlEnumItem.getUrl()),
null,
null,
null,
null,
null,
null),
commandController,
commandController.getClass().getDeclaredMethod("commandHandler", HttpServletRequest.class)
);
}
return requestMappingHandlerMapping;
}
and this is my controller method signature
#RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT}, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Object> commandHandler(final HttpServletRequest request) throws Exception {
// controller code here
}
If the url path is something like /api/test it works, but with something like /api/test/{idEntity} I don't have any PathVariable available in the request.
I tried everything like
String originalUrl = (String) request.getAttribute(
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
which returns the valued url (i.e. /api/test/1234), not the template, or adding
#PathVariable Map<String, Object> parameters
as a parameter in the method, which is empty.
Debugging the request object it seems there isn't anything useful to identify the path variables.
Maybe I should interrogate the HandlerMapping, but I can't have access to it in the controller method.
Is there a way to extract the pathVariables in the controller method?
It was an error in the configuration. I shouldn't have added the RequestMapping annotation to the controller method because it overrode my configuration.
Now I have
#RestController
public class CommandController extends AbstractController {
private final MappingJackson2JsonView mappingJackson2JsonView = new MappingJackson2JsonView();
#Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
// controller code here
return new ModelAndView(mappingJackson2JsonView);
}
}

Spring controllers composition instead of inheritance

I develop REST application based on Spring MVC.
I have following hierarhy of Controllers:
UserController extends ImageController
ImageController extends SuggestController // Controller that has GET-DELETE-POST for image
SuggestController extends CrudController// Controller that has some suggest methods
CrudController // CRUD operations
So I had following mappings in runtume:
/users (POST-GET-PUT-DELETE)
/users/suggest (GET, POST(Spring Pageable))
/users/image (POST-GET-DELETE)
It was ok before I realized, that one controller must be able to give me images of its stuff, but cannot implement "suggest" methods:
/stuff (POST-GET-PUT-DELETE)
/stuff/image (POST-GET-DELETE)
And another one does not have "image" functionality, but has "suggest":
/things (POST-GET-PUT-DELETE)
/things/suggest (GET, POST(Spring Pageable))
Java says : "Use composition in such cases":
StuffController {
#InlineThisController
ImageController imagedController;
#InlineThisController
CrudController crudController;
... //some additional methods
}
So how can I acheive this in Spring MVC without boilerplate for delegation? Annotations? XML?
Spring MVC will not allow you to override method annotated with #RequestMapping, or more exactly it does not allow you to annotate the overriding method with #RequestMapping and will use the mapping in base class.
But you can always define 2 methods in base class : one annotated with #RequestMapping that only delegates to second not annotated. Then you are then free to override the second method in subclasses. Example :
Abstract base class for a CRUD controller
public abstract class AbstractCRUDController<K extends Serializable, X>
// X is the data class, K is the class of the key (int or String)
#RequestMapping({"/{data}"})
public String show(#ModelAttribute("data") X data, HttpSession session, Model model,
#RequestParam(value = "pagesize", required = false, defaultValue = "-1") int pageSize,
WebRequest request,
#RequestParam(value = "all", defaultValue = "false") boolean all) {
return doShow(data, session, model, pageSize, request, all);
}
protected String doShow(X data, HttpSession session, Model model,
int pageSize, WebRequest request, boolean all) {
return this.getPrefix() + "/view";
}
#RequestMapping(value={"/{data}/edit"}, method=RequestMethod.GET)
public String edit(#ModelAttribute("data") X data, Model model) {
return doEdit(data, model);
}
protected String doEdit(#ModelAttribute("data") X data, Model model) {
model.addAttribute("title", editTitle);
return this.getPrefix() + "/edit";
}
#RequestMapping(value={"/{data}/edit"}, method=RequestMethod.POST)
public String update(#ModelAttribute("data") X data, BindingResult result, Model model) {
if (data == null) {
throw new NoSuchElementException();
}
if (save(data, result, model, SimpleSaveType.UPDATE, null) != null) {
return "redirect:" + savedUrl(data);
}
else {
model.addAttribute("title", editTitle);
return getPrefix() + "/edit";
}
}
public K save(X data, BindingResult result, Model model, SaveType saveType, K id) {
...
}
...
public abstract String getPrefix();
}
Concrete implementation for class ProcessQ
#Controller
#RequestMapping(ProcessController.PREFIX)
public class ProcessController extends AbstractCRUDController<Integer, ProcessQ> {
public static final String PREFIX = "/process";
#Override
public String getPrefix() {
return PREFIX;
}
#Override
protected String doShow(ProcessQ process, HttpSession session, Model model,
int pageSize, WebRequest request, boolean all) {
String viewName = super.doShow(process, session, model, pageSize, request, all);
// specialized processing for class ProcessQ
...
return viewName;
}
...
}
Example is taken from a real program, that's the reason why you can see elements for pagination, error processing and access to underlying request.
Here are possible approaches:
Use Lombok's #Delegate
Use Kotlin's delegation support
Use Java's default interface methods (looks uglier than 1 and 2)

Effective way how to handle ModelMap between Controllers, using forwarding in Spring MVC

What is the most elegant/effective way, how to handle Model between Controllers in Spring MVC 3.2. For redirecting to another Controller I use forward method, so there is not necessary new instance of request and Model data should be accessible (if I am not wrong). Is there any way how to catch Model, which was added in first Controller?
(I know about RedirectAttributes, but may be is better/easier method)
Example:
#Controller
public class WebpageController{
#RequestMapping( value = { "/{code}" } )
public String handleFirstLevel(#PathVariable String code, ModelMap modelMap) throws PageNotFoundEception{
final Webpage webpage = getWebpage(code);
modelMap.put(WEBPAGE_MODEL_KEY, prepareModel(webpage));
return "forward:some-url";
}
private Map<String, Object> prepareModel(Webpage webpage){
Map<String, Object> model = new HashMap<String, Object>();
model.put("webpage", webpage);
return model;
}
// some other code
}
#Controller
public class SpecialWebpageController{
#RequestMapping( value = { "/some-url" } )
public String handleFirstLevel(#PathVariable String code, ModelMap modelMap) throws PageNotFoundEception{
// need access to previously appended model to add some other data
return "specialViewName";
}
}
Thank you
When you have a handler method that simply returns a String, that String is considered a view name. With a prefix of forward, Spring will get a RequestDispatcher for the specified path and forward to it. Part of that process will include taking the Model from the ModelAndView created for that request handling cycle and putting all its attributes into the HttpServletRequest attributes.
The Servlet container will take the RequestDispatcher#forward(..) and again use your DispatcherServlet to handle it. Your DispatcherServlet will create a new ModelAndView with a new Model for this handling cycle. Therefore this Model doesn't contain any of the attributes from before but the HttpServletRequest attributes do.
In your case, this
modelMap.put(WEBPAGE_MODEL_KEY, prepareModel(webpage));
will end up being in
HttpServletRequest request = ...;
request.getAttribute(WEBPAGE_MODEL_KEY);

How to read flash attributes after redirection in Spring MVC 3.1?

I would like to know how to read a flash attributes after redirection in Spring MVC 3.1.
I have the following code:
#Controller
#RequestMapping("/foo")
public class FooController {
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(...) {
// I want to see my flash attributes here!
}
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public ModelAndView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new ModelAndView().setViewName("redirect:/foo/bar");
}
}
What I am missing?
Use Model, it should have flash attributes prepopulated:
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(Model model) {
String some = (String) model.asMap().get("some");
// do the job
}
or, alternatively, you can use RequestContextUtils#getInputFlashMap:
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(HttpServletRequest request) {
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
if (inputFlashMap != null) {
String some = (String) inputFlashMap.get("some");
// do the job
}
}
P.S. You can do return return new ModelAndView("redirect:/foo/bar"); in handlePost.
EDIT:
JavaDoc says:
A RedirectAttributes model is empty when the method is called and is
never used unless the method returns a redirect view name or a
RedirectView.
It doesn't mention ModelAndView, so maybe change handlePost to return "redirect:/foo/bar" string or RedirectView:
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public RedirectView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new RedirectView("/foo/bar", true);
}
I use RedirectAttributes in my code with RedirectView and model.asMap() method and it works OK.
Try this:
#Controller
public class FooController
{
#RequestMapping(value = "/foo")
public String handleFoo(RedirectAttributes redirectAttrs)
{
redirectAttrs.addFlashAttribute("some", "thing");
return "redirect:/bar";
}
#RequestMapping(value = "/bar")
public void handleBar(#ModelAttribute("some") String some)
{
System.out.println("some=" + some);
}
}
works in Spring MVC 3.2.2
For all those like me who were having problems with seeing the POST url in the browser when a validation would fail.
The POST url is a private url that should not be exposed to users but it was automatically rendered when a validation failed. i.e. if a field was below a minimum length. I was using #Valid. I wanted the original GET url of the form to show at all times even when validation bounced back to the form, so I did the following:
if (validation.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.story", validation);
redirectAttributes.addFlashAttribute("story", story);
return new ModelAndView("redirect:/january/2015");
where story is the form object representation, redirectAttributes are RedirectAttributes you put in the method signature and validation is the BindingResult. /january/2015 is the mapping to the GET controller where the form lives.
After this implementation, in the mapping for /january/2015, story comes in intact as follows:
Story story= (Story) model.asMap().get("story");
//story from the POST method
I had to augment my GET method and check if this was not null. If not null, then send this to the form else I would send a newly initialized Story type to the form as default behaviour before.
In this manner, I am able to return to the form with the bindingresults intact (errors show on form) but have my GET url in place of the post url.

Why does 'update()' not redirect to 'sox' using #ModelAttribute in Spring MVC 3.0?

At the moment I just want to test redirecting from the update method back to the sox method. But instead I get an error complaining about a missing "update.jsp".
#RequestMapping(value = "/sox/update", method = RequestMethod.POST)
#ModelAttribute("formWrapper")
public final String update(HttpServletRequest request,
#ModelAttribute("formWrapper") FormWrapper formWrapper,
BindingResult bindResult,
ModelMap model)
{
return "redirect:/sox";
}
#ModelAttribute("formWrapper")
FormWrapper setupForm()
{
FormWrapper formWrapper = new FormWrapper();
return formWrapper;
}
#RequestMapping(value = "/sox", method = RequestMethod.GET)
public final String sox(HttpServletRequest request, ModelMap model)
{
return "sox";
}
I think your problem is the #ModelAttribute on the update method. Try it this way :
#RequestMapping(value = "/sox/update", method = RequestMethod.POST)
public final String update(HttpServletRequest request,
#ModelAttribute("formWrapper") FormWrapper formWrapper,
BindingResult bindResult,
ModelMap model)
{
return "redirect:/sox";
}
When you add the #ModelAttribute to a method spring treats the return as a model attribute :
Any other return type is considered to
be a single model attribute to be
exposed to the view, using the
attribute name specified through
#ModelAttribute at the method level
(or the default attribute name based
on the return type class name). The
model is implicitly enriched with
command objects and the results of
#ModelAttribute annotated reference
data accessor methods.
Look at 15.3.2.8 of the Spring Docs for more info: (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib)

Categories

Resources