So when you go to /appointments the get() action is called, so then would the view be get.jsp (assuming you are using .jsp, and assuming you are mapping action names to views)?
And what about the getnewform? It seems to be returning an object? Is that basically passed into the view?
#Controller #RequestMapping("/appointments") public class AppointmentsController {
private final AppointmentBook appointmentBook;
#Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
#RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
#RequestMapping(value="/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(#PathVariable #DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
#RequestMapping(value="/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
#RequestMapping(method = RequestMethod.POST)
public String add(#Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
} }
In the example, the #RequestMapping is used in a number of places. The first usage is on the type (class) level, which indicates that all handling methods on this controller are relative to the /appointments path. The get() method has a further #RequestMapping refinement: it only accepts GET requests, meaning that an HTTP GET for /appointments invokes this method. The post() has a similar refinement, and the getNewForm() combines the definition of HTTP method and path into one, so that GET requests for appointments/new are handled by that method.
#RequestMapping-annotated methods can return a wide variety of objects, including a View, a Model, a Map, a String, and so on. These return values are interpreted by ServletHandlerMethodInvoker.getModelAndView(), which constructs a ModelAndView objects based on that return value.
In cases where the return value does not specify a view name (in your example, every method other than add() returns no view name), then Spring will attempt to automatically resolve the view name. By default, this is done by DefaultRequestToViewNameTranslator, which uses the information about the request to choose the view name. The examples in the javadoc are:
http://localhost:8080/gamecast/display.html -> display
http://localhost:8080/gamecast/displayShoppingCart.html -> displayShoppingCart
http://localhost:8080/gamecast/admin/index.html -> admin/index
Note that the selected view name is not based upon the information in the #RequestMapping methods, but on the properties of the request itself.
Related
I have a controller that has a few methods that get an optional of entity from service, checks if is present and proceeds with some other actions or redirects with message "Entity not found".
It looks like that:
#GetMapping("action")
public String method(#PathVariable Long id,
final RedirectAttributes redirectAttributes){
Optional<Entity> eOpt = entityService.findById(id);
if(eOpt.isEmpty()){
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
return "redirect:/entity/list"
}
Entity e = eOpt.get();
// other actions that are using e
return "view-name";
}
The six lines repeat in a few methods and for different entities too. Is there a way to assign it to some private method? The only thing I came up with is using a private method like:
private Optional<Entity> getEntityOpt(Long id){
Optional<Entity> eOpt = entityService.findById(id);
if(eOpt.isEmpty()){
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
}
return Optional.empty();
}
This only saves me one line in mapped methods, so I don't have to set up alert message. Otherwise I still have to check again if the Optional is empty to redirect it.
So I guess the question really is - can I set up the private method to either return entity or redirect like:
Entity e = getEntityOrRedirect(Long id);
or maybe you have different ways to handle that problem. Or maybe it is what it is and you have to repeat yourself...
You may treat empty Optional as an exceptional situation.
In that case you may provide your own RuntimeException containing path to redirect.
public class EntityNotFoundException extends RuntimeException {
private final String fallbackView;
public EntityNotFoundException(final String fallbackView) {
this.fallbackView = fallbackView;
}
public String getFallbackView() {
return fallbackView;
}
Then provide a method annotated with #ExceptionHandler to your controller class (or if the situation is common for multiple controllers then provide such method to class annotated with #ControllerAdvice). Your exception handler should catch just defined exception and do a redirect.
#ExceptionHandler(EntityNotFoundException.class)
public String redirectOnEntityNotFoundException(final EntityNotFoundException exception,
final RedirectAttributes redirectAttributes) {
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
return exception.getFallbackView();
}
Finally you achieved some kind of getEntityOrRedirect. Now you may use the above setup as following:
#GetMapping("action")
public String method(#PathVariable Long id){
Entity e = entityService.findById(id)
.orElseThrow(() -> new EntityNotFoundException("redirect:/entity/list"));
// other actions that are using e
return "view-name";
}
Code not tested so apologize for typos in advance.
Note I believe it would work for Spring >= 4.3.5 as otherwise RedirectAttributes wouldn't be resolved for #ExceptionHandler (as stated here)
I have a view that is rendered with this method:
#RequestMapping("/employee/{id}")
public String showSpecificEmployee(#PathVariable String id, Model model){
model.addAttribute("employee", employeeService.findEmployeeById(new Long(id)));
DateCommand dateCommand = new DateCommand();
dateCommand.setEmployeeId(new Long(id));
model.addAttribute("date", dateCommand);
return "specificEmployee";
}
The view displayes some basic information about the Employee. On the same view, I do have a form to choose a month and filter the information by Date.
After the Date is chosen, I would like to have the view 'refreshed' with updated information. That means I do have a POST & GET methods bound to the same view.
#RequestMapping("/passdate")
public String updateWorkmonth(#ModelAttribute DateCommand dateCommand, Model model){
model.addAttribute("employee", employeeService.findEmployeeWithFilteredWorkdaysAndPayments(dateCommand.getEmployeeId(), dateCommand.getActualDate()));
model.addAttribute("date", dateCommand);
return "specificEmployee";
}
After the second method is invoked looks like
http://localhost:8080/passdate?employeeId=1&actualDate=2018-02, but I want it to be /employee/{id}. How do I combine those 2 methods, so they point to the same URL?
If I set #RequestMapping("/employee/{id}") on both methods, I keep getting an error.
You actually need only one GET method
#RequestMapping("/employee/{id}")
and optionally passed
#RequestParam("actualDate")
You can redirect user to that url. Just replace in method updateWorkmonth one line
return "specificEmployee";
with
return "redirect:/employee/" + dateCommand.getEmployeeId();
You can specify the type of HTTP request you want in the #RequestMapping parametters
When you don't specify it, it uses GET by default
#RequestMapping(value = "/employee/{id}",method = RequestMethod.POST)
I have an object that I fill in a form in the view "submit".
After that, it post the object "WelcomeMessageFinder" in the view "return".
I call a service with that use this object. If the service fails, I want to redirect to the view "submit" and keep the form filled with the previous values.
My issue is that I don't find how to keep the "WelcomeMessageFinder" object after the redirect. It always create a new empty object.
Here is my code :
#Controller
#SessionAttributes("welcomeMessageFinder")
public class SandBoxController extends PortalWebuiController {
#ModelAttribute("welcomeMessageFinder")
public WelcomeMessageFinder welcomeMessageFinder() {
return new WelcomeMessageFinder();
}
#RequestMapping(value = "/submit", method = RequestMethod.GET)
public String submit(WelcomeMessageFinder welcomeMessageFinder, Model model, SessionStatus sessionStatus, HttpSession httpSession) {
// On Init : a new WelcomeMessageFinder is created
// After redirect : I want to keep the filled WelcomeMessageFinder but a new one is created ...
model.addAttribute("zenithUserSession", zenithUserSession);
return "submitwelcomemessage";
}
#RequestMapping(value = "/return", method = RequestMethod.POST)
public String retun(
WelcomeMessageFinder welcomeMessageFinder,
Model model,
RedirectAttributes redirectAttributes,
SessionStatus sessionStatus, HttpSession httpSession) {
// welcomeMessageFinder contains the parameters I enter in the form.
redirectAttributes.addFlashAttribute("welcomeMessageFinder", welcomeMessageFinder);
return "redirect:/submit";
}
}
What can I do to keep the same WelcomeMessageFinder object before and after the redirect ?
I find this question that says that I can't use SessionAttributes with redirect because it doesn't keep the session. And it says to use RedirectAttributes but the attributes seems to be reinitialized.
EDIT :
I finally found my error. This code works, the problem is with my class WelcomeMessageFinder. To add an object in the flash session, this object need to be Serializable. I forget to implements Serializable in my class.
After adding this, it works fine.
I finally found my error. This code works, the problem is with my class WelcomeMessageFinder. To add an object in the flash session, this object need to be Serializable. I forget to implements Serializable in my class.
After adding this, it works fine.
it is because of this piece of code
"#ModelAttribute("welcomeMessageFinder")
public WelcomeMessageFinder welcomeMessageFinder() {
return new WelcomeMessageFinder();
}"
. it is ALWAYS executed before any requestmapping method is called
I have a simple question, but I cannot find a solution anywhere.
For a project I have a controller which pulls lists according to some business rules. I have multiple RequestMappings and multiple methods but they should all return the same view. Is there a way to specify a default view for a controller?
Currently my code looks like this:
#Controller
public class OverviewController {
#RequestMapping("/{name}-games")
public String getOverview(#PathVariable("name") String name) {
// Code
return "view";
}
#RequestMapping("/{category}")
public String getCategory(#PathVariable("category") String category) {
// Code
return "view";
}
#RequestMapping("/special-{promo}-games")
public String getSpecialPromo(#PathVariable("promo") String namepromo) {
// Code
return "view";
}
}
I can replace the return "view"; with something like return view(); everywhere but I am hoping to find something more like an annotation:
#DefaultView()
public String view() {
return "view";
}
I am unable to find any such thing in the spring documentation. Is this possible or is the whole setup wrong to start with?
According to the Sping Reference,
The RequestToViewNameTranslator interface determines a logical View name when no such logical view name is explicitly supplied.
(That is when your controller method returns Model, Map or void.)
You could implement this interface, but I think in your example the best thing you can do is defining a constant as CodeChimp has suggested.
Can you not go with the approach of having multiple view resolvers using order??
Have the beanNameViewResolver with order 0,which tries to map the matching bean to the modelAndView(common for controller in your case) that you return.
In case it doesnt match then you can default it onto a internalResourceViewResolver(order=1) to provide default behaviour .
Your default view page required some attributes that should be send via Model Attributes.Assuming,these required model attributes are same in all your methods of different Business logic.You can add them in Flash attributes and redirect to default Method.
Suppose X1,X2 attributes are same in all Handler method independent of Logic
#Controller
#SessionAttribute({"X1","X2"})
public class OverviewController {
#RequestMapping("/{name}-games")
public String getOverview(#PathVariable("name") String name,final RedirectAttributes redirectAttributes) {
// Code
//add attributes requires for view in Flash attribute
redirectAttributes.addFlashAttribute("X1", "X1");
redirectAttributes.addFlashAttribute("X2", "X2");
return "redirect:defaultview";
}
#RequestMapping("/{category}")
public String getCategory(#PathVariable("category") String category,final RedirectAttributes redirectAttributes) {
// Code
//add attributes requires for view in Flash attribute
redirectAttributes.addFlashAttribute("X1", "X1");
redirectAttributes.addFlashAttribute("X2", "X2");
return "redirect:defaultview";
}
#RequestMapping("/special-{promo}-games")
public String getSpecialPromo(#PathVariable("promo") String namepromo,final RedirectAttributes redirectAttributes) {
// Code
//add attributes requires for view in Flash attribute
redirectAttributes.addFlashAttribute("X1", "X1");
redirectAttributes.addFlashAttribute("X2", "X2");
return "redirect:defaultview";
}
#RequestMapping("defaultview")
public String default(Model model) {
//here you can access all attributes in Flash Map via Model Attribute
// Code
model.addAttribute("X1","X1");
model.addAttribute("X1","X1");
return "view";
}
}
Caution:you have to add requires attributes in Session also because if you refresh page ,this avoids well known Exception.
Thank you
In Spring Framework , AbstractWizardFormController seems deprecated. How to implement multiple pages form in the Spring MVC Framework. (I am not using webflow)
any example or pointer would help considering my limited knowledge in Spring.
A #Controller is a more flexible way to define a form / wizard. You are supposed to map methods to requests based on requested path / request parameters / request method. So instead of defining a list of views and processing the request based on some required "step" parameter, you can define the steps of your wizard as you wish (also the command object will be handled more transparently). Here's how you can get to emulate a classic AWFC functionality (this is only meant to be an example, there's a lot more you can do).
#Controller
#RequestMapping("/wizard.form")
#SessionAttributes("command")
public class WizardController {
/**
* The default handler (page=0)
*/
#RequestMapping
public String getInitialPage(final ModelMap modelMap) {
// put your initial command
modelMap.addAttribute("command", new YourCommandClass());
// populate the model Map as needed
return "initialView";
}
/**
* First step handler (if you want to map each step individually to a method). You should probably either use this
* approach or the one below (mapping all pages to the same method and getting the page number as parameter).
*/
#RequestMapping(params = "_step=1")
public String processFirstStep(final #ModelAttribute("command") YourCommandClass command,
final Errors errors) {
// do something with command, errors, request, response,
// model map or whatever you include among the method
// parameters. See the documentation for #RequestMapping
// to get the full picture.
return "firstStepView";
}
/**
* Maybe you want to be provided with the _page parameter (in order to map the same method for all), as you have in
* AbstractWizardFormController.
*/
#RequestMapping(method = RequestMethod.POST)
public String processPage(#RequestParam("_page") final int currentPage,
final #ModelAttribute("command") YourCommandClass command,
final HttpServletResponse response) {
// do something based on page number
return pageViews[currentPage];
}
/**
* The successful finish step ('_finish' request param must be present)
*/
#RequestMapping(params = "_finish")
public String processFinish(final #ModelAttribute("command") YourCommandClass command,
final Errors errors,
final ModelMap modelMap,
final SessionStatus status) {
// some stuff
status.setComplete();
return "successView";
}
#RequestMapping(params = "_cancel")
public String processCancel(final HttpServletRequest request,
final HttpServletResponse response,
final SessionStatus status) {
status.setComplete();
return "canceledView";
}
}
I tried to vary the method signatures so that you can get an idea about the flexibility I mentioned. Of course, there's a lot more to it: you can make use of request method (GET or POST) in the #RequestMapping, you can define a method annotated with #InitBinder, etc.
EDIT: I had an unmapped method which I fixed (by the way, you need to make sure you don't have ambiguous mappings -- requests that could be mapped to more than one method -- or unmapped requests -- requests that cannot be mapped to any method). Also have a look at #SessionAttributes, #SessionStatus and #ModelAttribute, which are also needed for fully simulating the behaviour of the classic AWFC (I edited the code already to make this clear).