Get #RequestMapping value of different method in same controller - java

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.

Related

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.

Accept multiple params in a single PathVariable

I have a controller method as this:
#PostMapping("/view/{location}")
public ModelAndView view(#PathVariable("location") String location) {
ModelAndView modelAndView = new ModelAndView();
return modelAndView;
}
This method is capable of receiving requests like
"/view/a" or "/view/b" such that pathVariable location becomes a or b.
But I want this same method to receive all the requests having /view in their beginning, such that the pathVariable "location" holds the rest of the data.
for example
for a request as /view/a/b/c, the pathVariable location will become a/b/c.
like a file system hierarchy.
Please let me know if such a thing is possible in Spring MVC, and I am very new at this.
Check out this article
The idea is to map all the paths which start with /view to a single controller method by adding **, but you'll have to use HttpServletRequest instead of #PathVariable.
So, in your case, it'll be something like this:
#PostMapping("/view/**")
public ModelAndView view(HttpServletRequest request) {
String pathVariable = extractId(request);
ModelAndView modelAndView = new ModelAndView();
return modelAndView;
}
private String extractId(HttpServletRequest request) {
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
return new AntPathMatcher().extractPathWithinPattern(bestMatchPattern, path);
}
Also, check out this question
You could go by the approach shared earlier,
#GetMapping(value = "blog/**")
public Blog show(HttpServletRequest request){
String id = (String)
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
System.out.println(id);
int blogId = Integer.parseInt(id);
return blogMockedData.getBlogById(blogId);
}
Second way is to use RequestParam instead of Path variable.
you will call the api using :
http://localhost:8080/blog?input=nabcd/2/nr/dje/jfir/dye
controller will look like :
#GetMapping(value = "blog")
public Blog show(#RequestParam("input") String input){
If you are certain about the number of slash in your input, you could go with any approach mentioned here help

What is the difference between #ModelAttribute, model.addAttribute in spring?

i am new Spring learner.i'm really confused about what is the difference between two concept:
#ModelAttribute
model.addAttribute
in below there are two "user" value.Are these same thing?Why should I use like this?
Thank you all
#RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
model.addAttribute("user", new User());
return "editUser";
}
#RequestMapping(method = RequestMethod.POST)
public String processSubmit( #ModelAttribute("user") User user, BindingResult result, SessionStatus status) {
userStorageDao.save(user);
status.setComplete();
return "redirect:users.htm";
}
When used on an argument, #ModelAttribute behaves as follows:
An #ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument’s fields should be populated from all request parameters that have matching names. This is known as data binding in Spring MVC, a very useful mechanism that saves you from having to parse each form field individually.
http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-ann-modelattrib-method-args
That's a very powerful feature. In your example, the User object is populated from the POST request automatically by Spring.
The first method, however, simply creates an instance of Userand adds it to the Model. It could be written like that to benefit from #ModelAttribute:
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#ModelAttribute User user) {
// user.set...
return "editUser";
}

After pressing back button session doesnot destroy

am facing a strange issue.
my logout code is as :
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String thanks(HttpSession session) {
session.removeAttribute("parentEmail");
session.invalidate();
return "redirect:parent-login";
}
but after logout i get link as /logout?email=xyz#xyz.com
so when i press back button am again on last accessed page and can update the data.
email I have set as session attribute.
Can anybody tell me why am getting this URL.
By default all model attributes are considered to be exposed as URI
template variables in the redirect URL. Of the remaining attributes
those that are primitive types or collections/arrays of primitive
types are automatically appended as query parameters.
use redirectAttributes.addAttribute() to append required query parameters.
You should also invalidate any authentication related object from ModelMap by setting that object null
model.addAttribute("parentLogin",null);
So your method should look like:
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String thanks(Model model,RedirectAttributes redirectAttributes,HttpSession session) {
redirectAttributes.addAttribute("logout", "1234");
model.addAttribute("parentLogin",null);
session.removeAttribute("parentEmail");
session.invalidate();
return "redirect:parent-login";
}

Can #PathVariable return null if it's not found?

Is it possible to make the #PathVariable to return null if the path variable is not in the url? Otherwise I need to make two handlers. One for /simple and another for /simple/{game}, but both do the same just if there is no game defined i pick first one from a list however if there is a game param defined then i use it.
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable("example") String example,
HttpServletRequest request) {
And this is what I get when trying to open page /simple:
Caused by: java.lang.IllegalStateException: Could not find #PathVariable [example] in #RequestMapping
They cannot be optional, no. If you need that, you need two methods to handle them.
This reflects the nature of path variables - it doesn't really make sense for them to be null. REST-style URLs always need the full URL path. If you have an optional component, consider making it a request parameter instead (i.e. using #RequestParam). This is much better suited to optional arguments.
As others have already mentioned No you cannot expect them to be null when you have explicitly mentioned the path parameters. However you can do something like below as a workaround -
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable Map<String, String> pathVariablesMap,
HttpServletRequest request) {
if (pathVariablesMap.containsKey("game")) {
//corresponds to path "/simple/{game}"
} else {
//corresponds to path "/simple"
}
}
If you are using Spring 4.1 and Java 8 you can use java.util.Optional which is supported in #RequestParam, #PathVariable, #RequestHeader and #MatrixVariable in Spring MVC
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable Optional<String> game,
HttpServletRequest request) {
if (game.isPresent()) {
//game.get()
//corresponds to path "/simple/{game}"
} else {
//corresponds to path "/simple"
}
}
You could always just do this:
#RequestMapping(value = "/simple", method = RequestMethod.GET)
public ModelAndView gameHandler(HttpServletRequest request) {
gameHandler2(null, request)
}
#RequestMapping(value = "/simple/{game}", method = RequestMethod.GET)
public ModelAndView gameHandler2(#PathVariable("game") String game,
HttpServletRequest request) {
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable(value="example",required = false) final String example)
Try this approach, it worked for me.
I just tested this just now, but by combining the above solution i got this:
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable(value = "game", required = false) String example,
HttpServletRequest request) {
if (example != null) {
//...
} else {
//pick first, ...
}
}
Now when you use "/simple", String example will be null instead of throwing Exception.
Short solution, no fancy Optional<> or Map<>
We can write multiple methods in controllers with explicit mapping with the path variable combination to exclude the optional variables (if using old version of Spring)
In my scenario wanted to develop an API to get recycle value for old device where parameters could be brand, model and network however network is an option one.
One option to handle this was use network as a request parameter instead of pathVariable.
for e.g. /value/LG/g3?network=vodafone however I didn't like this approach.
for me the more cleaner one was to use below
/refurbValue/LG/g3
/refurbValue/LG/g3/vodafone
#RequestMapping(value = "/refurbValue/{make}/{model}/{network}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
def getRefurbValueByMakeAndModelAndNetwork(#PathVariable String make, #PathVariable String model, #PathVariable String network ) throws Exception {
//logic here
}
#RequestMapping(value = "/refurbValue/{make}/{model}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
def getRefurbValueByMakeAndModel(#PathVariable String make, #PathVariable String model) throws Exception {
//logic here
}
In the above example, both controller can use the same service method and handling of the parameter can be done. In my case I was using Groovy so it was easy to use with optional parameter like
Map getRefurbValue(String brand, String model, String network="")

Categories