I have a request mapping for a controller let's say it's A, it receives post action and uses its post values as parameter, sometimes the parameters will be very long so that's the reason why it's POST not GET apart from the best practices and security;
RequestMapping(value = "/reports/performance/indicator/{indicatorType}", method = RequestMethod.POST)
public String generatePerformanceReportsIndicator(ModelMap map,HttpServletResponse response, #PathVariable("indicatorType") Long indicatorType,
#RequestParam(value = "siteIds", required = false) List<Long> siteIds,
#RequestParam(value = "timeframeIds", required = false) List<String> timeframeIds,
#RequestParam(value = "showTarget", required = false) String showTarget,Locale locale) {
And then it turned out that in another spring controller I need to forward the request to the first one.
The problem is how I can add post parameters to the request before forwarding it to the first request mapping? is that healthy to say for example?
new FirstController().generatePerformanceReportsIndicator(....);
Given that:
I don't want the first request mapping to use get instead of post of the mentioned reasons.
I don't want to write redundant code by creating another method that extract the parameters as attribute from the model map.
You should not manually call other controllers! What you can do is redirect to them with RedirectAttributes like:
#RequestMapping(value = "/doctor/doEditPatientDetails", method = RequestMethod.POST)
public String editPatientDetails(Model model, #ModelAttribute(value = "user") #Valid User user,
BindingResult result, RedirectAttributes attr, Principal principal) {
if (null != principal) {
if (result.hasErrors()) {
attr.addFlashAttribute("org.springframework.validation.BindingResult.user", result);
attr.addFlashAttribute("user", user);
attr.addAttribute("id", user.getId());
return "redirect:/doctor/editPatient/{id}";
}
}
....
return "redirect:/doctor/patients";
}
#RequestMapping(value = "/doctor/editPatient/{id}", method = RequestMethod.GET)
public String showEditPatient(Model model, #ModelAttribute("id") String id, Principal principal) {
if (null != principal) {
//here you can access the model and do what everything you want with the params.
if (!model.containsAttribute("user")) {
model.addAttribute("user", user);
}
....
}
return "/doctor/editPatient";
}
Note that, to redirect to a link like "redirect:/doctor/editPatient/{id}" you have to add the id in RedirectAttributes. Also not that there are many ways you can achive the same functionality like HttpServletRequest
Related
I have created a liferay portlet application using Spring, thymeleaf and AngularJS. For communication between AngularJS and spring I need to create some rest calls which I have created using #ResourceMapping like as shown below. The application is working fine but the problem is that I don't know how to make GET, DELETE, PUT http REST calls since #ResourceMapping is not allowing to specify any methods.
#ResourceMapping(value="getUserDetail")
public void userDetail(#RequestParam long userId, ResourceResponse response) throws Exception {
Users users = new Users(userId);
// some logic
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
JSON_MAPPER.writeValue(response.getPortletOutputStream(), users);
}
When I used #RequestMapping instead of #ResourceMapping like as shown below
#RequestMapping(value="getUserDetail", method=RequestMethod.GET)
#ResponseBody
public void userDetail(#RequestParam long userId, ResourceResponse response) throws Exception {
System.out.println("Got detail request for user with id {} "+ userId);
// UserDetail userDetail = this.userService.getPortalUserDetail(userId);
List<String> users = new ArrayList<String>();
users.add("Manu");
users.add("Lissie");
users.add("John");
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
JSON_MAPPER.writeValue(response.getPortletOutputStream(), users);
}
I have got
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping': Initialization of bean failed; nested exception is java.lang.IllegalStateException: Mode mappings conflict between method and type level: [getUserDetail] versus [view]
Can anyone please tell me some solution for this
How to create different types of http calls using #ResourceMapping
Can we use #RequestMapping instead of #ResourceMapping in Liferay Spring portlet for REST calls
How can we create resource based REST urls like getUser/12/mumbai
How can we send REST json as body instead of Request Param
Mode mappings conflict exception
The question doesn't show it, but your controller probably has #RequestMapping("view") annotation. This type level mapping is in conflict with the method level mappings. You should remove #RequestMapping annotation on the controller class.
Request mapping examples
#Controller
public class SampleRESTFullController {
// Simple GET
#RequestMapping(value = "/helloSample", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<HelloSample> helloSample() { ... }
// GET with path variable
#RequestMapping(value = "/helloSample/sampleId/{sampleId}", method = RequestMethod.GET)
public #ResponseBody HelloSample helloSample(#PathVariable("sampleId") Long sampleId) { ... }
// POST with #RequestBody
#RequestMapping(value = "/helloSample", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public #ResponseBody HelloSample createSample(#RequestBody HelloSample helloSample) { ... }
// PUT with path variable and #RequestBody
#RequestMapping(value = "/helloSample/sampleId/{sampleId}", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.NO_CONTENT)
void update(#PathVariable("sampleId") long sampleId, #RequestBody HelloSample helloSample) { ... }
// DELETE
#RequestMapping(value = "/helloSample/sampleId/{sampleId}", method = RequestMethod.DELETE)
#ResponseStatus(HttpStatus.NO_CONTENT)
void delete(#PathVariable("sampleId") long sampleId) { ... }
}
I took the examples from Using RESTFul services with Liferay blog post. It answers all your questions and presents tons of examples. Pay attention to Spring configuration, which makes the RESTful services possible (especially the view resolver and message converter).
1. How to create different types of http calls using #ResourceMapping
If you want to a REST Api with Complete Actions (GET, POST, PUT, DELETE) you need to use #RequestMapping.
2. Can we use #RequestMapping instead of #ResourceMapping in Liferay Spring portlet for REST calls
You should be able to use.
3. How can we create resource based REST urls like getUser/12/mumbai
#RequestMapping(value="getUser/{userId}/mumbai", method=RequestMethod.GET)
#ResponseBody
public List<String> userDetail(#RequestParam("userId") long userId) throws Exception {
System.out.println("Got detail request for user with id {} "+ userId);
//UserDetail userDetail = this.userService.getPortalUserDetail(userId);
List<String> users = new ArrayList<String>();
users.add("Manu");
users.add("Lissie");
users.add("John");
return users;
}
4. How can we send REST json as body instead of Request Param
You can use #RequestBody
#RequestMapping(value="saveUser/{userId}", method=RequestMethod.GET)
#ResponseStatus(HttpStatus.CREATED)
public void userDetail(#RequestParam("userId") long userId, #RequestBody User user) throws Exception {
// Logic
}
How to create different types of http calls using #ResourceMapping
Here are some examples that may help you, that's how i use #RequestMapping:
// GET
#RequestMapping(value = "/api/something", method = RequestMethod.GET)
#ResponseBody
public boolean getSomething() {
return "something";
}
// GET with param
#RequestMapping(value = "/api/something/{id}", method = RequestMethod.GET)
#ResponseBody
public boolean getSomething(#PathVariable("id") Long id) {
return id;
}
Instead of RequestMethod.GET you can use RequestMethod.POST,RequestMethod.PUT,RequestMethod.DELETE and so on...
How can we send REST json as body instead of Request Param
Here is a code snippet that i currently use with an AngularJS FrontEnd for user registration. It works just fine and i use #RequestMapping:
#ResponseBody
#RequestMapping(value = "/auth/register", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> register(#RequestBody User user) {
user = userService.initUser(user);
Authentication authentication = securityUserDetailsService.register(user);
if (authentication != null) {
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
User authUser = securityUserDetailsService.getAuthenticatedUser();
return new ResponseEntity<>(authUser, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
In order to consume JSON you do:
RequestMapping(value = "/whatever", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
In order to produce (return) JSON you do:
RequestMapping(value = "/whatever", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
Also since you use Spring i think you should take a look at Spring Data and Spring Data Rest. This way you can expose your business models as RESTful endpoints.
How can we create resource based REST urls like getUser/12/mumbai
So in order to expose this endpoint getUser/12/mumbai that's what you should do:
// mumbai hardcoded
#RequestMapping(value = "/getUser/{id}/mumbai", method = RequestMethod.GET)
#ResponseBody
public User getUser(#PathVariable("id") Long id) {
// go get the user ...
return user;
}
// mumbai as a param
#RequestMapping(value = "/getUser/{id}/{prop}", method = RequestMethod.GET)
#ResponseBody
public User getUser(#PathVariable("id") Long id, #PathVariable("prop") String prop) {
// go get the user ...
return user;
}
Lastly can you please try to change
public void userDetail (...
to this
public ResponseEntity<userDetail > (...
There are following methods to use rest app with angular js
#RequestMapping(value = "/saveuser", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#RequestMapping(value = "/getemployee", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#RequestMapping(value = "/editCountry", method = RequestMethod.PUT)
#RequestMapping(value = "/deleteCountry", method = RequestMethod.DELETE)
and use following javascript to communicate with spring controller
var formData = {
"userName" : 'Vasim',
"password" : '123456',
"roleName" : 'Admin'
};
var response = $http.post('add', formData);
response.success(function(data, status, headers, config) {
$scope.message = data;
});
var formData = {
"userName" : 'Vasim',
"password" : '123456',
"roleName" : 'Admin'
};
var response = $http.put('edit', formData);
response.success(function(data, status, headers, config) {
$scope.message = data;
});
$scope.delete= function(employeeId) {
$http['delete']('delete', {
params : {
'employeeId' : employeeId
}
}).
success(function(data) {
$scope.msg = data;
});
$http.get('get',{params:{
'id':id
}
}).success(function(data) {
$scope.employees = data;
Is it possible for a Spring controller to handle both kind of requests?
1) http://localhost:8080/submit/id/ID123432?logout=true
2) http://localhost:8080/submit/id/ID123432?name=sam&password=543432
If I define a single controller of the kind:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout", required = false) String logout,
#RequestParam("name") String username,
#RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
the HTTP request with "logout" is not accepted.
If I define two controllers to handle each request separately, Spring complains with the exception "There is already 'Controller' bean method ... mapped".
Before Java 8 and Spring 5 (but works with Java 8+ and Spring 5+ too)
You need to give required = false for name and password request parameters as well. That's because, when you provide just the logout parameter, it actually expects for name and password because they are still "implicitly" mandatory.
It worked when you just gave name and password because logout wasn't a mandatory parameter thanks to required = false already given for logout.
Update for Java 8 and Spring 5 (and above)
You can now use the Optional class from Java 8 onwards to make the parameters optional.
#RequestMapping (value = "/path", method = RequestMethod.GET)
public String handleRequest(#RequestParam("paramName") Optional<String> variableName) {
String paramValue = variableName.orElse("");
// use the paramValue
}
As part of Spring 4.1.1 onwards you now have full support of Java 8 Optional (original ticket) therefore in your example both requests will go via your single mapping endpoint as long as you replace required=false with Optional for your 3 params logout, name, password:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout") Optional<String> logout,
#RequestParam("name") Optional<String> username,
#RequestParam("password") Optional<String> password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
Create 2 methods which handle the cases. You can instruct the #RequestMapping annotation to take into account certain parameters whilst mapping the request. That way you can nicely split this into 2 methods.
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"logout"})
public String handleLogout(#PathVariable("id") String id,
#RequestParam("logout") String logout) { ... }
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"name", "password"})
public String handleLogin(#PathVariable("id") String id, #RequestParam("name")
String username, #RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model, BindingResult errors)
throws LoginException {...}
In case someone is looking for mapping Optional parameters with Pojo, same can be done like below.
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
LoginRequest loginRequest,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
#Data
#NoArgsConstructor
//#AllArgsConstructor - Don't use this
public class LoginRequest {
private Optional<String> logout = Optional.empty();
private Optional<String> username = Optional.empty();
private Optional<String> password = Optional.empty();
}
Note: Do not use #AllArgsConstructor on POJO else it will initialize the fields as null.
I have a requirement where the user selects some data from a form and we need to show that selected data on the next page.
At present we are doing this with a session attribute, but the problem with this is that it overwrites the data if the first page is open in another browser tab, where the data is again selected and submitted. So I just want to get rid of this session attribute while transferring data from one controller to another.
Note: I am using an XML based Spring configuration, so please show a solution using XML, not annotations.
Define RedirectAttributes method parameter in the the handler method that handles form submission from first page:
#RequestMapping("/sendDataToNextPage", method = RequestMethod.POST)
public String submitForm(
#ModelAttribute("formBackingObj") #Valid FormBackingObj formBackingObj,
BindingResult result,
RedirectAttributes redirectAttributes) {
...
DataObject data = new DataObject();
redirectAttributes.addFlashAttribute("dataForNextPage", data);
...
return "redirect:/secondPageURL";
}
The flash attributes are saved temporarily before the redirect (typically in the session) and are available to the request after the redirect and removed immediately.
The above redirect will cause the client (browser) to send a request to /secondPageURL. So you need to have a handler method to handle this request, and there you can get access to the DataObject data set in the submitForm handler method:
#RequestMapping(value = "/secondPageURL", method = RequestMethod.GET)
public String gotoCountrySavePage(
#ModelAttribute("dataForNextPage") DataObject data,
ModelMap model) {
...
//data is the DataObject that was set to redirectAttributes in submitForm method
return "pageToBeShown";
}
Here DataObject data is the object that contains data from the submitForm method.
I worked with this requirement and I used RedirectAttributes, then you can add this redirect attributes to your model. This is an example:
#RequestMapping(value = "/mypath/{myProperty}", method = RequestMethod.POST)
public String submitMyForm(#PathVariable Long myProperty, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("message", "My property is: " + myProperty);
return "redirect:/anotherPage";
}
#RequestMapping(method = RequestMethod.GET)
public String defaultPage(Model model, #RequestParam(required = false) String message) {
if(StringUtils.isNotBlank(message)) {
model.addAttribute("message", message);
}
return "myPage";
}
Hope it helps.
You can use RedirectAttributes ; A specialization of the Model interface that controllers can use to select attributes for a redirect scenario.
public interface RedirectAttributes extends org.springframework.ui.Model
Plus this interface also provide a way to store "Flash Attribute" . Flash Attribute is in FlashMap.
FlashMap : A FlashMap provides a way for one request to store attributes intended for use in another. This is most commonly needed when redirecting from one URL to another.
Quick Example is
#RequestMapping(value = "/accounts", method = RequestMethod.POST)
public String handle(RedirectAttributes redirectAttrs) {
// Save account ...
redirectAttrs.addFlashAttribute("message", "Hello World");
return "redirect:/testUrl/{id}";
}
Reference and detail information are here
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.
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)