Spring: #RequestMapping with multiple values - java

I don't know this is the right approach but the easiest solution I can come up with in the moment.
I want to use multiple values in #RequestMapping and do the business logic in the method according which value is called the method. Example:
#RequestMapping(value = {"/delete", "/save"}, method = RequestMethod.POST)
public String crudOps(#ModelAttribute ("userForm") User user) {
// find user in repository....
if(value is delete) // don't know how to make this check
delete(user);
else
save(user);
}
How can I make that if statement work?

fter adding a comment above, I thought of a different solution by accessing the HttpServletRequest getServletPath method,
#RequestMapping(value = {"/delete", "/save"}, method = RequestMethod.POST)
public String crudOps(#ModelAttribute ("userForm") User user, HttpServletRequest request) {
// find user in repository....
if(request.getServletPath().equals("/delete"))
delete(user);
else
save(user);
}

You can use #PathVariale to grab part of the URL as follows
#RequestMapping(value = /{action}, method = RequestMethod.POST)
public String crudOps(#PathVariable String action, #ModelAttribute ("userForm") User user) {
// find user in repository....
if(action.equals("delete"))
delete(user);
else
save(user);}

I would personally go ahead with something like below,
#RequestMapping(value = "user/{userid}", method = RequestMethod.DELETE)
And use
#RequestMapping(value = "/save", method = RequestMethod.POST)
for creating an user. This helps in Single Responsibility Principle and more modular code. Let your method do one thing, and do it right!
Would recommend this link for using which HttpMethod for which purpose.

Related

Correct way to put parameters in a function

I have a huge form with around 30 parameters and I don't think it's a good idea to do what I usually do.
The form will be serialized and pass all the parameters via ajax post to spring controller.
I usually do like this:
#RequestMapping(value = "/save-state", method = RequestMethod.POST)
public #ResponseBody
void deleteEnvironment(#RequestParam("environmentName") String environmentName, #RequestParam("imageTag") String imageTag) {
//code
}
but if I have 30 parameters I will have a huge parameter list in the function.
What is the usual and correct way to avoid this?
EDIT: What if I pass the HttpServlet request only?? The request will have all the parameters and I can simple call request.getParameters("").
There are two options I would suggest:
Take an HttpServletRequest object and fetch needed properties separately.
The problem is Spring's controllers are designed to eliminate such low-level API (Servlets API) calls. It's could be the right fit if a controller was too abstract (operates on abstract datasets), which means you wouldn't be able to define a DTO with a fixed-length number of parameters.
Construct a DTO class with the properties needed and take it as a parameter.
The advantage is you completely delegate low-level work to Spring and care only about your application logic.
You can do something like this:
#RequestMapping(value = "/save-state", method = RequestMethod.POST)
public void deleteEnvironment(#RequestBody MyData data) {
//code
}
Create a class containing all your form parameters and receive that on your method.
but if I have 30 parameters I will have a huge parameter list in the
function.
In your request, pass a JSON object that contains these information and create its counterpart in Java.
RequestMethod.POST is not design to perform deletion.
Usr rather RequestMethod.DELETE.
#RequestMapping(value = "/save-state", method = RequestMethod.DELETE)
public #ResponseBody
void deleteEnvironment(MyObject myObject) {
//code
}
Correct way is to serialize all parameters as Json and call back end api with one parameter.
On back-end side get that json and parse as objects.
Example:
` #RequestMapping(method = POST, path = "/task")
public Task postTasks(#RequestBody String json,
#RequestParam(value = "sessionId", defaultValue = "-1") Long sessionId)
throws IOException, AuthorizationException {
Task task = objectMapper.readValue(json, Task.class);
`

Detect invalid URL parameters using Spring framework

Is there a good way within the Spring framework to detect when an incoming URL has an invalid parameter? It seems like the default behavior is to ignore unrecognized parameters. The best solution I can find involves adding a parameter mapping to all my endpoints and check that mapping against the parameters it is expecting.
For example, say I have a widget site with a collection endpoint.
#RequestMapping(value = "/widgets", method = RequestMethod.GET)
public ResponseEntity<WidgetList> getWidgets(
#RequestParam(value = "search", required = false) String search) {
// ...
// Get list of widgets
// ...
return new ResponseEntity<WidgetList>(widgetList, HttpStatus.OK);
}
The "search" parameter is optional because leaving it out is a convenience allowing all widgets to be found. I support a search syntax such that the following finds widgets where the foo attribute has a value of bar
GET https://example.com/widgets?search=foo:bar
A user makes a typo
GET https://example.com/widgets?saerch=foo:bar
This fails silently. Instead of finding widgets where foo=bar, all are found. I'd like it to return a 400 error stating that the "saerch" parameter is not supported. A great answer would be some sort of strict option on RequestMapping, like the following.
#RequestMapping(value = "/widgets", method = RequestMethod.GET, paramsStrict = true)
public ResponseEntity<WidgetList> getWidgets(
#RequestParam(value = "search", required = false) String search) {
// ...
// Get list of widgets
// ...
return new ResponseEntity<WidgetList>(widgetList, HttpStatus.OK);
}
As far as I know such doesn't exist. I haven't figured out a clean way to intercept the request and check for all methods (and somehow communicate which parameters are valid for each method). The best I've figured out so far is to add a parameter map and check the map against accepted parameters in every single controller method.
#RequestMapping(value = "/widgets", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<WidgetList> getWidgets(
#RequestParam(value = "search", required = false) String search,
#RequestParam Map<String, String> allRequestParams) {
validateParameters(allRequestParms);
// ...
// Get list of widgets
// ...
return new ResponseEntity<WidgetList>(widgetList, HttpStatus.OK);
}
Is there a better way to do this?
Please don't post answers about my design or how I could make the search parameter required. That's beside the point I'm trying to make with a simple example. In my real-world application there are well-designed cases where checking for invalid parameter names would be useful.
You can implement your own Servlet Filter or HandlerInterceptor to validate parameters.
Following example with Filter:
public class ParametersValidationFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (validateParameters((HttpServletRequest)request, (HttpServletResponse)response)) {
chain.doFilter(request, response);
}
}
private boolean validateParameters(HttpServletRequest request, HttpServletResponse response) {
// Check parameter names in request.getParameterNames()
/*
Invalid parameter yields response.setStatus(HttpServletReponse.SC_BAD_REQUEST)
and additional info in response body
*/
// Otherwise, validation succeeds:
return true;
}
/* Other methods */
}
Also, Filter can be configurated with init method.
This 'filter-or-interceptor' way is better due to ability to reuse as well SOLID at all.

Determine route in multi route controller method

That defines multi route method in controller in Spring MVC
#RequestMapping(value={"/path", "/path2"}, method = RequestMethod.GET)
public String MyMethod () {
// Determine which route invoked the method
return null;
}
Is there a way to determine which route invoked the method?
Appreciate your kind help.
You could use HttpServletRequest which has a method called getRequestURL() to retrieve the actual URL, allowing you to parse which path was used.
However, another possibility is using path variables instead:
#RequestMapping(value = "/{path}", method = RequestMethod.GET)
public String myMethod(#PathVariable String path) {
// Do stuff with "path"
return null;
}
In this case, the path variable will contain whatever you enter matching the path given in your #RequestMapping, in your case it would be "path" or "path2". However, this will also allow other path variables as well ("path3" for example, ...), so you might want to validate it first before using.
I believe you can use HttpServletRequest:
#RequestMapping(value={"/path.html", "/path2.html"}, method = RequestMethod.GET)
public String MyMethod (HttpServletRequest request) {
// Determine which route invoked the method
String url = new String(request.getRequestURL());
log.debug("URL: " + url); //use whatever you use to log
return null;
}

How to keep an model attribute after a redirection?

I have an object that I fill in a form in the view "submit".
After that, it post the object "WelcomeMessageFinder" in the view "return".
I call a service with that use this object. If the service fails, I want to redirect to the view "submit" and keep the form filled with the previous values.
My issue is that I don't find how to keep the "WelcomeMessageFinder" object after the redirect. It always create a new empty object.
Here is my code :
#Controller
#SessionAttributes("welcomeMessageFinder")
public class SandBoxController extends PortalWebuiController {
#ModelAttribute("welcomeMessageFinder")
public WelcomeMessageFinder welcomeMessageFinder() {
return new WelcomeMessageFinder();
}
#RequestMapping(value = "/submit", method = RequestMethod.GET)
public String submit(WelcomeMessageFinder welcomeMessageFinder, Model model, SessionStatus sessionStatus, HttpSession httpSession) {
// On Init : a new WelcomeMessageFinder is created
// After redirect : I want to keep the filled WelcomeMessageFinder but a new one is created ...
model.addAttribute("zenithUserSession", zenithUserSession);
return "submitwelcomemessage";
}
#RequestMapping(value = "/return", method = RequestMethod.POST)
public String retun(
WelcomeMessageFinder welcomeMessageFinder,
Model model,
RedirectAttributes redirectAttributes,
SessionStatus sessionStatus, HttpSession httpSession) {
// welcomeMessageFinder contains the parameters I enter in the form.
redirectAttributes.addFlashAttribute("welcomeMessageFinder", welcomeMessageFinder);
return "redirect:/submit";
}
}
What can I do to keep the same WelcomeMessageFinder object before and after the redirect ?
I find this question that says that I can't use SessionAttributes with redirect because it doesn't keep the session. And it says to use RedirectAttributes but the attributes seems to be reinitialized.
EDIT :
I finally found my error. This code works, the problem is with my class WelcomeMessageFinder. To add an object in the flash session, this object need to be Serializable. I forget to implements Serializable in my class.
After adding this, it works fine.
I finally found my error. This code works, the problem is with my class WelcomeMessageFinder. To add an object in the flash session, this object need to be Serializable. I forget to implements Serializable in my class.
After adding this, it works fine.
it is because of this piece of code
"#ModelAttribute("welcomeMessageFinder")
public WelcomeMessageFinder welcomeMessageFinder() {
return new WelcomeMessageFinder();
}"
. it is ALWAYS executed before any requestmapping method is called

How to set a multiple parameters using String's Controller and RestTemplate?

Hi I need to modify an existent Rest interface. The old Rest interface only take a phone number then look for a record. The phone number used to be unique with the table. Now is the combination of the phone and a number (batchid). So i need to modify the service impl and the client that calls it. Here is the old controller:
#RequestMapping(value="/{phone}", method = RequestMethod.GET)
#ResponseBody
public BatchDetail findByPhone(#PathVariable String phone) {
return batchDetailService.findByPhone(phone);
}
and here is the how the old client is accesing it:
private static final String URL_GET_BATCHDETAIL_PHONE = "http://localhost:8080/Web2Ivr/restful/batchdetail/{phone}";
batchdetail = restTemplate.getForObject(URL_GET_BATCHDETAIL_PHONE, BatchDetail.class, phone);
batchdetail.setStatus("OK");
restTemplate.put(URL_TO_UPDATE_BATCHDETAIL, batchdetail, batchdetail.getId())
;
So my question will be how to modify the controller and the restemplate's client call to support two variables, phone and the number(batchid) something like:
http://localhost:8080/Web2Ivr/restful/batchdetail/{batchid}/{phone}
#RequestMapping(value="/{batchid}/{phone}", method = RequestMethod.GET)
#ResponseBody
public BatchDetail findByPhone(#PathVariable String phone, #PathVariable String batchid) {
return batchDetailService.findByPhone(phone);
}

Categories