Multiple endpoints for REST api in springboot - java

I have two unique keys in a table id, userId. I have to create a REST api in spring to get user details if any of the two keys are given as path variables.
The challenge here is we have to create two different endpoints for getting user through id and getting user through userId but use same method for both. Also datatype of id is long and datatype of userId is String in table
So I am trying to do following
the endpoint "/user/{id}" is for userId
#RequestMapping(value = {"/{id}","/user/{id}"}, method=RequestMethod.GET)
public response getUser(#PathVariable("id") String id){
}
But I am unable to figure out how to check whether I got id or userId inside the method. Also is this the right way to do it?

You can do this with single method like this:
#RequestMapping(value = {"/{id}", "/user/{userId}"}, method = RequestMethod.GET)
public void getUser(#PathVariable(value = "id", required = false) String id,
#PathVariable(value = "userId", required = false) Long userId) {
if (id != null) {
//TODO stuff for id
}
if (userId != null) {
//TODO stuff for userId
}
}

I would use the #RequestMapping Multiple paths mapped to the same controller method possibility in such a manner.
I suspect that even if you refactor your code to call one single method, you still have to implement some logic to differentiate between the two parameters inside the controller method.
Besides, getUserById() signature is ambiguous. What the parameter id means the id or userId
Having two separate method for what you want to acheive would be more efficient two handle each case properly. Inside each controller method you can use a common logic for the two if you want.
#RequestMapping(value = "/user/{userId}", method=RequestMethod.GET)
public String getUserById(#PathVariable("userId") String userId){
// Common login
}
#RequestMapping(value = "/id", method=RequestMethod.GET)
public String getUserByUserId(#PathVariable("userId") String userId){
// Common login
}
You can even implement for each endpoint validators to check either you #PathVariable is valid or not in case of Long or String
Here are some references ref1, ref2

Aren't you able to refactor the database to have only one id? That makes things clear and keeps the code cleaner.
If that is not possible you can create 2 methods with meaningful names.
// handles: /user/{entityId}
#RequestMapping(value = "/user/{entityId}", method=RequestMethod.GET)
public UserDto getUserByEntityId(#PathVariable("entityId") long entityId){
// call service
}
// handles: /user?userId={userId}
#RequestMapping(value = "/user", method=RequestMethod.GET)
public UserDto getUserByUserId(#RequestParam("userId", required=true) String userId){
// call service
}
You can discuss about about the correct names/signatures of the methods.
Another advantages of this approach is that you are able to add Swagger doc annotations to each of them.

Related

How would you handle a REST API with only optional query params with Spring Boot?

I want to build a simple endpoint that returns an Order object where I can search for this order by a single query parameter or a combination of several query parameters altogether. All of these query parameters are optional and the reason is that different people will access these orders based on the different Ids.
So for example:
/order/items?itemId={itemId}&orderId={orderId}&deliveryId={deliveryId}&packetId={packetId}
#GetMapping(path = "/order/items", produces = "application/json")
public Order getOrders(#RequestParam Optional<String> itemId,
#RequestParam Optional<String> orderId,
#RequestParam Optional<String> deliveryId,
#RequestParam Optional<String> packetId) { }
I could of course also skip the Java Optional and use #RequestParam(required = false), but the question here is rather how do I escape the if-else or .isPresent() nightmare of checking whether the query params are null? Or is there an elegant way, depending on the constellation of params, to pass further to my service and Spring Data JPA repository.
To minimize the amount of parameters in your method, you could define your query parameters as fields of a class:
#Data
public class SearchOrderCriteria {
private String itemId;
private String orderId;
private String deliveryId;
private String packetId;
}
Then receive an instance of such class in your controller method:
#GetMapping(path = "/order/items", produces = "application/json")
public ResponseEntity<OrderInfo> getOrder(SearchOrderCriteria searchCriteria) {
OrderInfo order = orderService.findOrder(searchCriteria)
return ResponseEntity.ok(order);
}
And, in your service, to avoid a bunch of if-else, you could use query by example:
public OrderInfo findOrder(SearchOrderCriteria searchCriteria) {
OrderInfo order = new OrderInfo();
order.setItemId(searchCriteria.getItemId());
order.setOrderId(searchCriteria.getOrderId());
order.setDeliveryId(searchCriteria.getDeliveryId());
order.setPacketId(searchCriteria.getPacketId());
Example<OrderInfo> example = Example.of(order);
return orderRepository.findOne(example);
}
My small suggestion is avoid to use too general API, for example you can split your mapping into several endpoints eg:/order/delivery/itemId/{itemId} and /order/delivery/deliveryId/{deliveryId} and /order/delivery/packetId/{packetId} and handle which you need to call on client side.

Difference between path and value attributes in #RequestMapping annotation

What is the difference between below two attributes and which one to use when?
#GetMapping(path = "/usr/{userId}")
public String findDBUserGetMapping(#PathVariable("userId") String userId) {
return "Test User";
}
#RequestMapping(value = "/usr/{userId}", method = RequestMethod.GET)
public String findDBUserReqMapping(#PathVariable("userId") String userId) {
return "Test User";
}
As mentioned in the comments (and the documentation), value is an alias to path. Spring often declares the value element as an alias to a commonly used element. In the case of #RequestMapping (and #GetMapping, ...) this is the path property:
This is an alias for path(). For example #RequestMapping("/foo") is equivalent to #RequestMapping(path="/foo").
The reasoning behind this is that the value element is the default when it comes to annotations, so it allows you to write code in a more concise way.
Other examples of this are:
#RequestParam (value → name)
#PathVariable (value → name)
...
However, aliases aren't limited to annotation elements only, because as you demonstrated in your example, #GetMapping is an alias for #RequestMapping(method = RequestMethod.GET).
Just looking for references of AliasFor in their code allows you to see that they do this quite often.
#GetMapping is a shorthand for #RequestMapping(method = RequestMethod.GET).
In your case.
#GetMapping(path = "/usr/{userId}") is a shorthand for #RequestMapping(value = "/usr/{userId}", method = RequestMethod.GET).
Both are equivalent. Prefer using shorthand #GetMapping over the more verbose alternative. One thing that you can do with #RequestMapping which you can't with #GetMapping is to provide multiple request methods.
#RequestMapping(value = "/path", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT)
public void handleRequet() {
}
Use #RequestMapping when you need to provide multiple Http verbs.
Another usage of #RequestMapping is when you need to provide a top level path for a controller. For e.g.
#RestController
#RequestMapping("/users")
public class UserController {
#PostMapping
public void createUser(Request request) {
// POST /users
// create a user
}
#GetMapping
public Users getUsers(Request request) {
// GET /users
// get users
}
#GetMapping("/{id}")
public Users getUserById(#PathVariable long id) {
// GET /users/1
// get user by id
}
}
#GetMapping is an alias for #RequestMapping
#GetMapping is a composed annotation that acts as a shortcut for #RequestMapping(method = RequestMethod.GET).
value method is an alias for path method.
This is an alias for path(). For example #RequestMapping("/foo") is equivalent to #RequestMapping(path="/foo").
So both methods are similar in that sense.

Get #RequestMapping value of different method in same controller

Using Spring Boot, I have a couple methods in my RegisterController which handles new user registration.
The createNewUser method is responsible for saving the new user to the database and sending a confirmation e-mail containing a link that has a unique token.
The confirmUser method handles processing the GET request for the confirmation link.
Is there a way for the createNewUser method to get the #RequestMapping value assigned to confirmUser? I'd like to use this value to generate the confirmation link instead of hard coding it.
// Process form input data
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView createNewUser(#Valid User user, BindingResult bindingResult) {
}
// Process confirmation link
// Link in confirmation e-mail will be /registerConfirmation?token=UUID
#RequestMapping(value="/registerConfirmation", method = RequestMethod.GET)
public ModelAndView confirmUser( #RequestParam("token") String token) {
}
I don't know of a way to get it from the #RequestMapping value but you have a couple of different options.
Option 1: Create a constant for the mapping and use that which allows you to reference it in both methods.
private final static String REGISTER_CONF_VAL = "/registerConfirmation";
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView createNewUser(#Valid User user, BindingResult bindingResult) {
}
// Process confirmation link
// Link in confirmation e-mail will be /registerConfirmation?token=UUID
#RequestMapping(value=REGISTER_CONF_VAL, method = RequestMethod.GET)
public ModelAndView confirmUser( #RequestParam("token") String token) {
}
Option 2: Less ideal, but if you add registerConfirmation to your config file, you can access it like:
#RequestMapping(value="${register.conf.val}", method = RequestMethod.GET)
public ModelAndView confirmUser( #RequestParam("token") String token) {
}
The reason this isn't ideal is because you probably don't want it to be different from environment to environment. That said, it would work.
If you need to generate the link based on user request, you can use the Path Variable in the controller. U can get the path variable and use some mechanism to validate the path as well.
Replace registerConfirmation with {registerConfirmation} and in the method, use #PathVariable annotation to get the path. Use the variable to check if the path is valid.

How to map a path variable to an entity in spring-rest

I got some REST endpoint in Spring-RS which use an entity id as path variable. Most of the time, the first thing the method is doing is retrieving the entity using the id. Is there a way to automatically map the id to the entity, having only the entity as methods parameters ?
Current situation :
#RequestMapping(path="/{entityId})
public void method(#PathVariable String entityId) {
Entity entity = entityRepository.findOne(entityId);
//Do some work
}
What I would like to have :
#RequestMapping(path="/{entityId})
public void method(#PathVariable Entity entityId) {
//Do some work
}
That's possible if you are using Spring Data. See official documentation
You can do something like this:
#RequestMapping(value = "/doctor/appointments/{start}/{end}", produces = "application/json")
#ResponseBody
public List<Appointment> showAppointments(Model model, #ModelAttribute("start") Date start,
#ModelAttribute("end") Date end, Principal principal) {
}
to get your custom object and Spring will try to convert the parameter to the given type.
I prefer to pass the id most of the times for security reasons.

Data binding of an abstract class in spring-mvc

I've went thru Spring documentation and source code and still haven't found answer to my question.
I have these classes in my domain model and want to use them as backing form objects in spring-mvc.
public abstract class Credentials {
private Long id;
....
}
public class UserPasswordCredentials extends Credentials {
private String username;
private String password;
....
}
public class UserAccount {
private Long id;
private String name;
private Credentials credentials;
....
}
My controller:
#Controller
public class UserAccountController
{
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid UserAccount account)
{
//persist in DB
return account.id;
}
#RequestMapping(value = "/listAccounts", method = RequestMethod.GET)
public String listAccounts()
{
//get all accounts from DB
return "views/list_accounts";
}
....
}
On UI I have dynamic form for the different credential types. My POST request usually looks like:
name name
credentials_type user_name
credentials.password password
credentials.username username
Following exception is thrown if I try to submit request to the server :
org.springframework.beans.NullValueInNestedPathException: Invalid property 'credentials' of bean class [*.*.domain.UserAccount]: Could not instantiate property type [*.*.domain.Credentials] to auto-grow nested property path: java.lang.InstantiationException
org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:628)
My initial thought was to use #ModelAttribute
#ModelAttribute
public PublisherAccount prepareUserAccountBean(#RequestParam("credentials_type") String credentialsType){
UserAccount userAccount = new PublisherAccount();
Class credClass = //figure out correct credentials class;
userAccount.setCredentials(BeanUtils.instantiate(credClass));
return userAccount;
}
Problem with this approach is that prepareUserAccountBean method get called before any other methods (like listAccounts) as well which is not appropriate.
One robust solution is to move out both prepareUserAccountBean and saveUserAccount to the separate Controller. It doesn't sound right : I want all user-related operations to reside in the same controller class.
Any simple solution? Can I utilize somehow DataBinder, PropertyEditor or WebArgumentResolver?
Thank you!!!!!
I can't see any simple and elegant solution. Maybe because the problem is not how to data bind abstract classes in Spring MVC, but rather : why having abstract classes in form objects in the first place ? I think you shouldn't.
An object sent from the form to the controller is called a "form (backing) object" for a reason : the object attributes should reflect the form fields. If your form has username and password fields, then you should have username and password attributes in your class.
So credentials should have a UserPasswordCredentials type. This would skip your "abstract instantiation attempt" error. Two solutions for this :
Recommended : you change the type of UserAccount.credentials from Credentials to UserPasswordCredentials. I mean, what Credentials could a UserAccount possibly have, except a UserPasswordCredentials ? What's more, I bet your database userAccounts have a username and password stored as credentials, so you could as well have a UserPasswordCredentials type directly in UserAccount. Finally, Spring recommends using "existing business objects as command or form objects" (see doc), so modifying UserAccount would be the way to go.
Not recommended : you keep UserAccount as is, and you create a UserAccountForm class. This class would have the same attributes as UserAccount, except that UserAccountForm.credentials has a UserPasswordCredentials type. Then when listing/saving, a class (UserAccountService for example) does the conversion. This solution involves some code duplication, so only use it if you have a good reason (legacy entities you cannot change, etc.).
I'm not sure, but you should be using ViewModel classes on your controllers instead of Domain Objects. Then, inside your saveAccount method you would validate this ViewModel and if everything goes right, you map it into your Domain Model and persist it.
By doing so, you have another advantage. If you add any other property to your domain UserAccount class, e.g: private bool isAdmin. If your web user send you a POST parameter with isAdmin=true that would be bind to user Domain Class and persisted.
Well, this is the way I'd do:
public class NewUserAccount {
private String name;
private String username;
private String password;
}
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid NewUserAccount account)
{
//...
}

Categories