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

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.

Related

How to test with Postman a controller's method that has one or more objects parameters (and not simple params) in Spring?

I am a newbie in Spring development. I need to create a simple application, a controller that has a method that takes as parameter an object of a custom designed entity class into the project. The prototype looks like this:
#RestController
public class JobsController {
#PostMapping("/search")
public ResponseEntity<?> search() {
log.info("JobsController -> search method");
//JobSearchEntity jobSearchEntity = modelMapper.map(jobSearch, JobSearchEntity.class);
List<JobEntity> jobs = jobService.searchJobs();
//log.info(String.format("Job found: %s ", jobSearch));
return ResponseEntity.ok(jobs);
}
}
Can someone who is more advanced into this staff with Postman testing tell me how to do that , how to test a controller method which takes parameters?
You can use postman to submit parameters in JSON format after adding # requestbody annotation on the method, or submit parameters directly in form without annotation
You can use this example. Is very simple exemple.
#RestController
#RequestMapping("/root")
public class RootController {
private final RootService service;
public RootController(final RootService service) {
this.service = service;
}
#PostMapping("/exemple")
public void createRoot(#RequestBody final RootDto dto) {
service.createRoot(dto);
}
}
Then you can send request to POST host/root/exemple with your JSON.
More exampls you can find here: https://www.baeldung.com/spring-request-response-body
It seems you are missing an honest search on google about the subject.
You can make use of #RequestBody annotation to accept method arguments.
Check these page for examples --
#RequestBody and #ResponseBody annotations in Spring
https://stackabuse.com/get-http-post-body-in-spring/
https://www.twilio.com/blog/create-rest-apis-java-spring-boot
These set of playlist on youtube are very good starter course for SpringBoot -
https://www.youtube.com/c/JavaBrainsChannel/playlists
Postman Tutorial--
https://www.youtube.com/watch?v=VywxIQ2ZXw4
To get data from api is preferred to use GET method :
#RestController
public class JobsController {
#GetMapping("/search")
public ResponseEntity<?> search(#RequestParam("id") String id,#RequestParam("desc") String desc) {
log.info("JobsController -> search method");
//JobSearchEntity jobSearchEntity = modelMapper.map(jobSearch, JobSearchEntity.class);
List<JobEntity> jobs = jobService.searchJobs();
//log.info(String.format("Job found: %s ", jobSearch));
return ResponseEntity.ok(jobs);
}
}
you call this api with post man this way :
#PostMapping used usually to save new data (example : create job )
Take look on rest resource naming guide

Multiple endpoints for REST api in springboot

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.

Spring restful API, is there a method being used like router to get other method's end points or URL?

#RequestMapping("/accounts")
public class controller {
#GetMapping("/get/{id}")
public final ResponseEntity<?> getHandler(){
}
#PostMapping(value = "/create")
public final ResponseEntity<?> createHandler(){
/*
trying to use some spring library methods to get the url string of
'/accounts/get/{id}' instead of manually hard coding it
*/
}
}
This is the mock code, now I am in createHandler, after finishing creating something, then I want to return a header including an URL string, but I don't want to manually concat this URL string ('/accounts/get/{id}') which is the end point of method getHandler(), so I am wondering if there is a method to use to achieve that? I know request.getRequestURI(), but that is only for the URI in the current context.
More explanation: if there is some library or framework with the implementation of route:
Routes.Accounts.get(1234)
which return the URL for the accounts get
/api/accounts/1234
The idea is, that you don't need to specify get or create (verbs are a big no-no in REST).
Imagine this:
#RequestMapping("/accounts")
public class controller {
#GetMapping("/{id}")
public final ResponseEntity<?> getHandler(#PathVariable("id") String id) {
//just to illustrate
return complicatedHandlerCalculation(id).asResponse();
}
#PostMapping
public final ResponseEntity<?> createHandler() {
//return a 204 Response, containing the URI from getHandler, with {id} resolved to the id from your database (or wherever).
}
}
This would be accessible like HTTP-GET: /api/accounts/1 and HTTP-POST: /api/accounts, the latter would return an URI for /api/accounts/2 (what can be gotten with HTTP-GET or updated/modified with HTTP-PUT)
To resolve this URI, you could use reflection and evaluate the annotations on the corresponding class/methods like Jersey does.
A Spring equivalent could be:
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
and
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(GetMapping.class).value()[0];
taken from How do i get the requestmapping value in the controller?

Spring MVC (RESTful API): Validating payload dependent on a path variable

Use Case:
let's design a RESTful create operation using POST HTTP verb - creating tickets where creator (assigner) specifies a ticket assignee
we're creating a new "ticket" on following location: /companyId/userId/ticket
we're providing ticket body containing assigneeId:
{
"assigneeId": 10
}
we need to validate that assigneeId belongs to company in URL - companyId path variable
So far:
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody Ticket newTicket, #PathVariable Long companyId, #PathVariable Long userId) {
...
}
we can easily specify a custom Validator (TicketValidator) (even with dependencies) and validate Ticket instance
we can't easily pass companyId to this validator though! We need to verify that ticket.assigneeId belongs to company with companyId.
Desired output:
ability to access path variables in custom Validators
Any ideas how do I achieve the desired output here?
If we assume that our custom validator knows desired property name, then we can do something like this:
Approach one:
1) We can move this getting path variables logic to some kind of a base validator:
public abstract class BaseValidator implements Validator {
#Override
public boolean supports(Class<?> clazz)
{
// supports logic
}
#Override
public void validate(Object target, Errors errors)
{
// some base validation logic or empty if there isn't any
}
protected String getPathVariable(String name) {
// Getting current request (Can be autowired - depends on your implementation)
HttpServletRequest req = HttpServletRequest((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (req != null) {
// getting variables map from current request
Map<String, String> variables = req.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
return variables.get(name);
}
return null;
}
}
2) Extend it with your TicketValidator implementation:
public class TicketValidator extends BaseValidator {
#Override
public void validate(Object target, Errors errors)
{
// Getting our companyId var
String companyId = getPathVariable("companyId");
...
// proceed with your validation logic. Note, that all path variables
// is `String`, so you're going to have to cast them (you can do
// this in `BaseValidator` though, by passing `Class` to which you
// want to cast it as a method param). You can also get `null` from
// `getPathVariable` method - you might want to handle it too somehow
}
}
Approach two:
I think it worth to mention that you can use #PreAuthorize annotation with SpEL to do this kind of validation (You can pass path variables and request body to it). You'll be getting HTTP 403 code though if validation woudnt pass, so I guess it's not exaclty what you want.
You could always do this:
#Controller
public class MyController {
#Autowired
private TicketValidator ticketValidator;
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#RequestBody Ticket newTicket,
#PathVariable Long companyId, #PathVariable Long userId) {
ticketValidator.validate(newTicket, companyId, userId);
// do whatever
}
}
Edit in response to the comment:
It doesn't make sense to validate Ticket independently of companyId when the validity of Ticket depends on companyId.
If you cannot use the solution above, consider grouping Ticket with companyId in a DTO, and changing the mapping like this:
#Controller
public class MyController {
#RequestMapping(value="/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody TicketDTO ticketDto,
#PathVariable Long userId) {
// do whatever
}
}
public class TicketDTO {
private Ticket ticket;
private Long companyId;
// setters & getters
}

Spring MVC: bind request attribute to controller method parameter

In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use #RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:
MyClass obj = (MyClass) request.getAttribute("attr_name");
But I really would like to use something like this instead:
#RequestAttribute("attr_name") MyClass obj
Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?
EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.
Well, I finally understood a little bit how models work and what is #ModelAttribute for. Here is my solution.
#Controller
class MyController
{
#ModelAttribute("user")
public User getUser(HttpServletRequest request)
{
return (User) request.getAttribute("user");
}
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String HandleSomeUrl(#ModelAttribute("user") User user)
{
// ... do some stuff
}
}
The getUser() method marked with #ModelAttribute annotation will automatically populate all User user parameters marked with #ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.
This solves exactly my problem however I do have further questions. Is there a common place where I can put those #ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?
Use (as of Spring 4.3) #RequestAttribute:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute User user) {
// ... do some stuff
}
or if the request attribute name does not match the method parameter name:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute(name="userAttributeName") User user) {
// ... do some stuff
}
I think what you are looking for is:
#ModelAttribute("attr_name") MyClass obj
You can use that in the parameters for a method in your controller.
Here is a link a to question with details on it What is #ModelAttribute in Spring MVC?
That question links to the Spring Documentation with some examples of using it too. You can see that here
Update
I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.
#RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
User user = new User(); //most likely you've done some kind of login step this is just for simplicity
return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}
Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.
#RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(#ModelAttribute("user") User user) {
user = accountService.updateDeliverySchedule(user); //do something with the user
return new ModelAndView("account", "user", user);
}
Not the most elegant, but works at least...
#Controller
public class YourController {
#RequestMapping("/xyz")
public ModelAndView handle(
#Value("#{request.getAttribute('key')}") SomeClass obj) {
...
return new ModelAndView(...);
}
}
Source : http://blog.crisp.se/tag/requestattribute
From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation.
This then would allow you to have an advice which adds the #ModelAttributes in a separate class, which is then applied to all your controllers.
For completeness, it is also possible to actually make the #RequestAttribute("attr-name") as is.
(below modified from this article to suit our demands)
First, we have to define the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestAttribute {
String value();
}
Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound
public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
// Get the annotation
RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);
if(requestAttributeAnnotation != null) {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getAttribute(requestAttributeAnnotation.value);
}
return UNRESOLVED;
}
}
Now all we need is to add this customresolver to the config to resolve it:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolver">
<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
</property>
</bean>
And we're done!
Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use #Peter Szanto's solution.
Alternatively, bind it as a ModelAttribute, as recommended in other answers.
As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:
#RequestMapping("/xyz")
public String index(Principal principle) {
return "Hello, " + principle.getName() + "!";
}
In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}
}
Then register it in RequestMappingHandlerAdapter

Categories