How to handle Ambiguous handler methods mapped in REST application with Spring - java

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.

Related

How to resolve [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported]

I am trying to Get, Delete and Post users' details.
Postman
I tried to send a request using Postman It showed 401 Unauthorized status.
I have tried every possible solution but none of them worked. I am getting this error on the browser -
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Nov 29 14:28:46 IST 2021
There was an unexpected error (type=Method Not Allowed, status=405).
UserController.java
#RestController
#RequestMapping("/user")
public class UserController {
#Autowired
private UserService userService;
// create user
#RequestMapping(value ="/" , method = RequestMethod.POST)
public Users createUser(#RequestBody Users users) throws Exception {
Set<UserRole> userroles = new HashSet<>();
Role role = new Role();
role.setRoleName("Student");
role.setRoleID(23L);
UserRole userRole = new UserRole();
userRole.setUsers(users);
userRole.setRoles(role);
userroles.add(userRole);
return this.userService.createUser(users, userroles);
}
#GetMapping("/{username}")
//#RequestMapping(value ="/{username}" , method = RequestMethod.GET)
public Users getUser(#PathVariable("username") String username){
return this.userService.getUser(username);
}
// #DeleteMapping("/{userId}")
#RequestMapping(value ="/{userId}" , method = RequestMethod.DELETE)
public void deleteUser(#PathVariable("userId") Long userId){
this.userService.deleteUser(userId);
}
UserServiceImplementation.java
#Service
public class UserServiceImplementation implements UserService {
// To save user
#Autowired
private UserRepository userRepository;
// To save role
#Autowired
private RoleRepository roleRepository;
#Override
public Users createUser(Users users, Set<UserRole> userRoles) throws Exception {
// check whether user is already on database
Users registered = this.userRepository.findByUsername(users.getUsername());
// if registered
if (registered != null){
System.out.println("User is already registered !");
throw new Exception("User is already present !");
}
// if not registered
else{
// create user
// user can have many roles so use for loop
// access each role one by one
for (UserRole ur : userRoles){
// save all roles in the role repo
roleRepository.save(ur.getRoles());
}
// assign all roles to the user
users.getUserRoles().addAll(userRoles);
registered = this.userRepository.save(users);
}
return registered;
}
// getting user by username
#Override
public Users getUser(String username) {
return this.userRepository.findByUsername(username);
}
#Override
public void deleteUser(Long userId){
this.userRepository.deleteById(userId);
}
}
Try to remove #RequestMapping("/user")
Instead of
#RequestMapping(value ="/" , method = RequestMethod.POST)
use
#PostMapping(value = "/")
type=Method Not Allowed
This comes when you are tryong to access endpoints in a wrong way from postman
Eg: using GET for a post endpoint etc, Can you please check

Spring REST controller with different functions

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);
}
}

no suitable HttpMessageConverter found for response type [class com.avada.rest.UsersController$Users]

I am getting the following exception and not sure why...
Exception in thread "main"
org.springframework.web.client.RestClientException: Could not extract
response: no suitable HttpMessageConverter found for response type
[class com.avada.rest.UsersController$Users] and content type
[application/json;charset=UTF-8] at
org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
at
org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:576)
at
org.springframework.web.client.RestTemplate.execute(RestTemplate.java:529)
at
org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:236)
at com.avada.rest.UsersTest.main(UsersTest.java:18)
This is my RestController:
#RestController
#RequestMapping("/users")
public class UsersController {
#RequestMapping(method = RequestMethod.GET)
public Users getUsers() {
Users users = new Users();
users.setUsers(ConstantsHome.userprofileMgr.getUsers(null));
return users;
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(#PathVariable String id) {
return ConstantsHome.userprofileMgr.getUserByUserId(id, true, true);
}
public static class Users {
private List<User> users = new ArrayList<>();
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
}
This is my Test class:
public class UsersTest {
private static RestTemplate template = new RestTemplate();
public static void main (String[] args) throws Exception {
// Get all users
String uri = "http://localhost:8080/IR360/rest/users";
UsersController.Users users = template.getForObject(uri, UsersController.Users.class);
System.out.println("Looping through users...");
for (User user : users.getUsers()) {
System.out.println("Name=" + user.getName());
}
// Get 1 user
uri = "http://localhost:8080/IR360/rest/users/admin";
User user = template.getForObject(uri, User.class);
System.out.println("Name for single user=" + user.getName());
}
}
I can get a single user no problem if I comment out the test code for "Get all users".
What am I doing wrong in this code?
P.S. - I can make a call to getUsers() through the browser and the json comes back fine so I know getUsers() is working...just can't get the RestTemplate to work
Turned out to be an issue in my Users class (more specifically the User class in List<User>).
I updated the User class with #JsonIgnore on fields that I thought might be causing the Exception and I was able to get passed this issue.
So for others that might encounter this issue, check the object you're trying to do a getForObject on to make sure everything can map fine.

Spring Rest Controller, Path Variables on an overriden method's arguement

I have a controller annotated with #RestController and it implements an interface:
public interface ContratEndpoint {
String ROOT = "/api/contrats";
String GET_CONTRAT = "";
String GET_CONTRAT_PER_PK = "/{idContrat}";
#RequestMapping(value = GET_CONTRAT)
Contrat getContrat(#RequestParam(value = "contratId")Long contratId);
#RequestMapping(value = GET_CONTRAT_PER_ID)
ExtContrat getContratById(#PathVariable("idContrat") Long idContrat);
}
The controller:
#RestController
#RequestMapping(value = ContratEndpoint.ROOT)
public class ContratController implements ContratEndpoint {
//Injecting Services....
#Resource
private Mapper mapper;
#Override
public Contrat getContrat(Long contratId) {
return mapper.map(contratService.get(contratId),Contrat.class);
}
#Override
public ExtContrat getContratById(#PathVariable("idContrat") Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
.The above Code works just fine.
. But For the first inherited method , I didn't have to annotate arguments with #RequestParam and it worked just fine.
As for the second method I tried at first :
#Override
public ExtContrat getContratById(Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
. I expected the same behaviour Like the first Method, But i was wrong and the code ended up firing an IllegalArgumentException because of the check in ligne Preconditions.checkArgument(idContrat!=null).
My question is what is so specific about #PathVariable that i've missed ?
Or is it just something is wrong with my approach?
Thanks.
There is difference between Request param and path variable,seee below post that you can confirm with your uri the cause for the exception :
#PathVariable is to obtain some placeholder from the uri (Spring call it an URI Template) — see Spring Reference Chapter 16.3.2.2 URI Template Patterns
#RequestParam is to obtain an parameter — see Spring Reference Chapter 16.3.3.3 Binding request parameters to method parameters with #RequestParam
Assume this Url http://localhost:8080/SomeApp/user/1234/invoices?date=12-05-2013 (to get the invoices for user 1234 for today)
#RequestMapping(value="/user/{userId}/invoices", method = RequestMethod.GET)
public List<Invoice> listUsersInvoices(
#PathVariable("userId") int user,
#RequestParam(value = "date", required = false) Date dateOrNull) {
...
}

"Chaining" two controllers?

A REST-API I am developing allows access to various kinds of user data.
Users can be identified via their Id, their email or their username. A user then has a couple of other data such as orders etc.
Now I am planning to expose the following endpoints:
/rest/user/byemail/test#example.org/profile
/rest/user/byemail/test#example.org/orders
/rest/user/byemail/test#example.org/address
/rest/user/byid/123456/profile
/rest/user/byid/123456/orders
/rest/user/byid/123456/address
/rest/user/byusername/test/profile
/rest/user/byusername/test/orders
/rest/user/byusername/test/address
As you can see, the URL always consists of two "parts": One for identifying the user and the other one for identifying the resource.
It would be great if I could now avoid writing 9 different methods in my controller (as there might be other types of information in the future).
Is it somehow possible to write one UserController which is then returned and parametrized by the MainController?
public class UserController {
#RequestMapping("/profile")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/orders")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/address")
public ModelAndView profile(User user) {
//...
}
}
#RequestMapping("/rest/user")
public class MainController {
#RequestMapping("byemail/{email}")
public ??? byEmail(#PathVariable String email) {
User user = //Find user by email
//???
}
#RequestMapping("byusername/{username}")
public ??? byUserName(#PathVariable String username) {
User user = //Find user by username
//???
}
#RequestMapping("byid/{id}")
public ??? byId(#PathVariable String id) {
User user = //Find user by id
//???
}
}
Or is it maybe possible to solve this via some kind of routing?
However, it would be important to "split" the URL and use one half to find the correct user which will then be available as a parameter when parsing the second half.
Why not just one controller with these request mappings?
#RequestMapping("/rest/user")
public class UserController {
#RequestMapping("{keyType}/{key}/orders")
public ModelAndView orders(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/profile")
public ModelAndView profile(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/address")
public ModelAndView address(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
private User findUser(String keyType, String key) {
// ...
}
}
Take a look at Spring Data REST.
This API exposes a SimpleJpaRespository to the web, accessible via RESTful paths.

Categories

Resources