I have a Spring #RestController for manipulating my Users and I want to have several functions:
/users : GET (returns all users)
/users/:id : GET (returns a user with given ID, default id=1)
/users : POST (inserts a user)
/users/:id : DELETE (deletes a user with given ID)
I started working on it but I'm not sure how to manage the "overlapping" URIs for the same method (e.g. first two cases). Here's what I came with so far:
#RestController
public class UserController {
#RequestMapping(value = "/users", method = RequestMethod.GET)
public List<User> getAllUsers() {
return UserDAO.getAll();
}
#RequestMapping(value = "/users", method = RequestMethod.GET)
public User getUser(#RequestParam(value = "id", defaultValue = "1") int id) {
return UserDAO.getById(id);
}
}
This won't work due to "ambiguous mapping" and it's pretty clear to me, but I don't know what to do. Should I change one of the URIs or there is some other way?
Edit:
I've also tried changing the second method to:
#RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public User getUser(#PathVariable("id") int id) {
return UserDAO.getById(id);
}
Still doesn't work.
Your current mapping:
#RequestMapping(value = "/users", method = RequestMethod.GET)
public User getUser(#RequestParam(defaultValue = "1") int id)
Would map to the /users?id=42 not the desired /users/42. If you want to create a mapping for /users/:id endpoint, use the following:
#RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public User getUser(#PathVariable int id) {
return UserDAO.getById(id);
}
Also, as of Spring Framework 4.3, you can use new meta annotations to handle GET, POST, etc. methods:
#RestController
#RequestMapping("/users")
public class UserController {
#GetMapping
public List<User> getAllUsers() {
return UserDAO.getAll();
}
#GetMapping("{id}")
public User getUser(#PathVariable int id) {
return UserDAO.getById(id);
}
}
Related
In my UserController, I have 3 #GetMapping methods:
getAllUsers() - It is used to get all users,
getUserById() - It is used to get specific user with his unique id and
getUserByName() - It is used to get specific user with his unique name.
The code from the method is as follows:
#RestController
#RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
#Autowired
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#GetMapping
public List<User> getAllUsers() {
return userRepository.findAll();
}
#GetMapping(value = "/{id}")
public ResponseEntity<User> getUserById(#PathVariable Long id) {
User user = userRepository.findById(id);
return ResponseEntity.ok(user);
}
#GetMapping(value = "/{name}")
public ResponseEntity<User> getUserByName(#PathVariable String name) {
User user = userRepository.findUserByName(name);
return ResponseEntity.ok(user);
}
The first problem is that my app doesn't know if the URL is String or Integer, so I solved the problem between the getUserById() and getUserByName() methods like this:
#GetMapping(value = "{id}")
public ResponseEntity<User> getUserById(#PathVariable Long id) {
User user = userRepository.findById(id);
return ResponseEntity.ok(user);
}
#GetMapping
public ResponseEntity<User> getUserByName(#RequestParam(value = "name") String name) {
User user = userRepository.findUserByName(name);
return ResponseEntity.ok(user);
}
So now I can access the methods with:
http://localhost:8080/users/1 and
http://localhost:8080/users?name=john
When I run the application, I get this error:
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'userController' method
com.db.userapp.controller.UserController#getUserByName(String)
to {GET [/users]}: There is already 'userController' bean method
I believe this is because my getAllUsers() and getUserByName() methods are on the same URL format. Does anyone have an idea how I can solve this?
You are right because both getAllUsers() and getUserByName() are mapped to the /users/. So for the request /users/ , it does not know which method should be used to process it.
You can configure the name query parameter for /users/ as optional and check if its value is null. Null means user does not want have any filtering on the users and want to get all users :
#RestController
#RequestMapping("/users")
public class UserController {
#GetMapping
public List<User> getUser(#RequestParam(value = "name",required = false) String name) {
if (Strings.isNullOrEmpty(name)) {
return userRepository.findAll();
} else {
return userRepository.findUserByName(name);
}
}
}
You're right. You have defined two GET Methods on /users and therefore its basically the same as your first problem.
You can merge these methods and set the RequestParam for name as not required and just use it when it's not null.
The problem here is that you would return one User as List even though you know that there is only one User for this name. Which I think is fine because its not the Users main identifier and with the RequestParam it's more like a filter anyway.
I was developing a simple CRUD application, when i've encountered a this weird error. Weird, because in my controller class convenient #RequestMapping annotated method is present with request GET method mapping. The requested URI is [context]/purchase/change/2. The error is following:
org.springframework.web.servlet.PageNotFound - Request method 'GET' not supported
and there is my controller:
#Controller
#RequestMapping("/purchase")
public class PurchasesController {
//...
#RequestMapping(value = "/add/{userId}", method = RequestMethod.GET)
public String addPurchase(Model model, #PathVariable int userId) {
//that method works with mapping ex. "context/purchase/add/1"
return "purchase_update_add";
}
#RequestMapping(value = "/add/{userId}", method = RequestMethod.POST)
public String addPurchase(
#ModelAttribute("purchase") PurchaseDTO purchaseDto,
#PathVariable int userId) {
//that works too
return "redirect:/user/" + userId;
}
#RequestMapping(value = "/change/${purchaseId}", method = RequestMethod.GET)
public String changePurchaseDate(Model model, #PathVariable int purchaseId) {
model.addAttribute("operation", "change");
PurchaseDTO purchase = new PurchaseDTO();
Purchase purchaseEntity = purchasesDAO.getPurchase(purchaseId);
purchase.setDate(purchaseEntity.getDate());
purchase.setId(purchaseId);
model.addAttribute("purchase", purchase);
return "purchase_update_add";
}
#RequestMapping(value = "/change/{userId}", method = RequestMethod.POST)
public String changePurchaseDate(
#ModelAttribute("purchase") PurchaseDTO purchaseDto,
#PathVariable int userId) {
Purchase purchase = purchasesDAO.getPurchase(purchaseDto.getId());
purchase.setDate(purchaseDto.getDate());
purchasesDAO.updatePurchase(purchase);
return "redirect:/user/" + userId;
}
You've created a Path Variable with ${...} syntax:
#RequestMapping(value = "/change/${purchaseId}", method = RequestMethod.GET)
^ $ is redundant
Use the correct {...} syntax instead:
#RequestMapping(value = "/change/{purchaseId}", method = RequestMethod.GET)
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;
With ModelAttribute annotation, we can feed many thing like listbox.
Is it better to feed each lisbox with a different method in the controler or to use a form and have a list of object for every listbox?
If the controller class is used for different requests where some have this listboxes and some not, (for example the Controller handles the show, create and update functions for an entity, where only the create and update pages have that list boxes) then popluating the model with the #ModelAttribute annotated method would mean, that this methods will be executed even if there values are not needed. -- I my humble opinnion this would be bad.
I hope I understand your question right, if not please add a example for each of the two choises you want to compare.
#RequestMapping("/users")
#Controller
TheWayIPreferController() {
#RequestMapping(params = "form", method = RequestMethod.GET)
public ModelAndView createForm() {
ModelMap uiModel = new ModelMap();
uiModel.addAttribute("userCreateCommand", new UserCreateCommand());
uiModel.addAttribute("securityRoles", this.securityRoleDao.readAll()));
uiModel.addAttribute("salutations", this.salutationDao.readAll()));
uiModel.addAttribute("locales", this.localeDao.readAll());
return new ModelAndView("users/create", uiModel);
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView create(final #Valid UserCreateCommand userCreateCommand, final BindingResult bindingResult) {
...
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ModelAndView show(#PathVariable("id") final User user) {
...
}
}
instead of:
#RequestMapping("/users")
#Controller
TheWayIDiscourageController(){
#ModelAttribute("securityRoles")
public List<SecurityRoles> getSecurityRoles(){
return this.securityRoleDao.readAll();
}
#ModelAttribute("salutations")
public List<SecurityRoles> getSalutations(){
return this.salutationDao.readAll());
}
#ModelAttribute("locales")
public List<SecurityRoles> getLocals(){
return this.localeDao.readAll();
}
#RequestMapping(params = "form", method = RequestMethod.GET)
public ModelAndView createForm() {
return new ModelAndView("users/create", "userCreateCommand", new UserCreateCommand());
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView create(final #Valid UserCreateCommand userCreateCommand, final BindingResult bindingResult) {
...
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ModelAndView show(#PathVariable("id") final User user) {
...
}
}
I am trying to map /my/route/id to an action but the parameter id keeps coming in as null. Does anyone know what I'm doing wrong?
Controller mapping code:
#Controller
#RequestMapping("/admin/alias")
public class AliasesController {
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public #ResponseBody SampleAliasMaskModel index(Integer id) {
Code issuing the request
$('select[name=aliasMask]', ctx).change(function () {
var val = parseInt($(this).val(), 10);
if (val === -1)
return maskSelected(null);
if (!_.isNaN(val))
$.getJSON('alias/'+val, {}, function(mask){
maskSelected(mask);
});
})
The request itself:
If spring isn't compiled with debug information you have to specify the name of the Pathvariable as annotation parameter.
Like this:
#Controller
#RequestMapping("/admin/alias")
public class AliasesController {
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public #ResponseBody SampleAliasMaskModel index(#PathVariable("id") Integer id) {
Maybe that's the reason.