I would like to achieve something like this with Spring MVC
#RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE)
#RequestMapping(value = "/user/{userId}/delete", method = RequestMethod.POST)
public void deleteUser(#PathVariable String userId) {
...
}
This would give me a common endpoint for REST calls and standard HTML form posts.
Is it possible to do with Spring MVC?
All I can come up with is
#RequestMapping(value = { "/user/{userId}", "/user/{userId}/delete"}, method = {RequestMethod.DELETE, RequestMethod.POST})
public void deleteUser(#PathVariable String userId) {
...
}
but the result is slightly different because a POST to "/user/{userId}" would also delete the user.
One thing you could do is make 2 separate methods with their own RequestMapping annotation, and then just pass the parameters on to a different method, where you do actual stuff:
#RequestMapping(value = "/user/{userId}/delete", method = RequestMethod.POST)
public void deleteUserPost(#PathVariable String userId) {
deleteUser(userId);
}
#RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE)
public void deleteUserDelete(#PathVariable String userId) {
deleteUser(userId);
}
private void deleteUser(String userId){
//Do things here
}
Sorry, got it wrong way around.
In mature REST architecture, code should use a URL to refer to a resource and use HTTP method to define the action on the resource. So just define a #RequestMapping("/user/{userId}/delete", method = RequestMethod.DELETE) and eliminate the POST. See DELETE vs POST.
Related
I have the following simple Java Controller with the Spring Web framework:
#RestController
#RequestMapping("/rounds")
#Slf4j
public class RoundController {
private RoundService roundService;
#Autowired
public RoundController(RoundService roundService) {
this.roundService = roundService;
}
#GetMapping(
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public List<Round> find() {
return roundService.find();
}
#GetMapping(
path = "/{userId}",
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public List<Round> get(#PathVariable String userId) {
return roundService.getRoundsByUserId(userId);
}
#PostMapping(
produces = MediaType.APPLICATION_JSON_VALUE
)
#ResponseStatus(HttpStatus.CREATED)
public Round create(#Valid #NotNull #RequestBody Round round) {
roundService.create(round);
return round;
}
#DeleteMapping(
path = "/{id}",
produces = MediaType.APPLICATION_JSON_VALUE
)
#ResponseStatus(HttpStatus.OK)
public void delete(#PathVariable String id) {
ObjectId objectId = new ObjectId(id);
roundService.delete(objectId);
}
}
When using Mongo is there a best practice for doing an update / patch for an object?
Is it best to just use the POST method, and re-save the Round object in the DB with the changes the user has made?
According to me, the best practice should not be the use of the POST for doing an update/patch.
Keep you POST to do only Round creation.
If you use spring data mongodb just call the save method of your repository with your entity
see https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/repository/MongoRepository.html
For an Update, better to add PUT /{roundId} in your controller and either :
call your save method if you have all the Round data
call a findById to have the full data and set the data you want to change, then save (but this is more like a PATCH)
Or you can also add a PATCH /{roundId} and update just the field you want in your document
see https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/MongoTemplate.html
I have a resource that supports both GET and POST requests. Here a sample code for a sample resource:
#RequestMapping(value = "/books", method = RequestMethod.GET)
public ModelAndView listBooks(#ModelAttribute("booksFilter") BooksFilter filter, two #RequestParam parameters, HttpServletRequest request)
throws ParseException {
LONG CODE
}
#RequestMapping(value = "/books", method = RequestMethod.POST)
public ModelAndView listBooksPOST(#ModelAttribute("booksFilter") BooksFilter filter, BindingResult result)
throws ParseException {
SAME LONG CODE with a minor difference
}
The code in the two methods is practically the same, except for lets say a variable definition. The two methods can be easily combined using method = {RequestMethod.POST, RequestMethod.GET}, and a simple if inside. I tried, but it doesn't work, because the two methods have a different parameter at the end, i.e. HttpServletRequest and BindingResult (the #RequestParam's are not required and therefore not needed in the POST request). Any ideas how to combine the two methods?
#RequestMapping(value = "/testonly", method = { RequestMethod.GET, RequestMethod.POST })
public ModelAndView listBooksPOST(#ModelAttribute("booksFilter") BooksFilter filter,
#RequestParam(required = false) String parameter1,
#RequestParam(required = false) String parameter2,
BindingResult result, HttpServletRequest request)
throws ParseException {
LONG CODE and SAME LONG CODE with a minor difference
}
if #RequestParam(required = true) then you must pass parameter1,parameter2
Use BindingResult and request them based on your conditions.
The Other way
#RequestMapping(value = "/books", method = RequestMethod.GET)
public ModelAndView listBooks(#ModelAttribute("booksFilter") BooksFilter filter,
two #RequestParam parameters, HttpServletRequest request) throws ParseException {
myMethod();
}
#RequestMapping(value = "/books", method = RequestMethod.POST)
public ModelAndView listBooksPOST(#ModelAttribute("booksFilter") BooksFilter filter,
BindingResult result) throws ParseException {
myMethod();
do here your minor difference
}
private returntype myMethod(){
LONG CODE
}
Below is one of the way by which you can achieve that, may not be an ideal way to do.
Have one method accepting both types of request, then check what type of request you received, is it of type "GET" or "POST", once you come to know that, do respective actions and the call one method which does common task for both request Methods ie GET and POST.
#RequestMapping(value = "/books")
public ModelAndView listBooks(HttpServletRequest request){
//handle both get and post request here
// first check request type and do respective actions needed for get and post.
if(GET REQUEST){
//WORK RELATED TO GET
}else if(POST REQUEST){
//WORK RELATED TO POST
}
commonMethod(param1, param2....);
}
#RequestMapping(value = "/books", method = { RequestMethod.GET,
RequestMethod.POST })
public ModelAndView listBooks(#ModelAttribute("booksFilter") BooksFilter filter,
HttpServletRequest request)
throws ParseException {
//your code
}
This will works for both GET and POST.
For GET if your pojo(BooksFilter) have to contain the attribute which you're using in request parameter
like below
public class BooksFilter{
private String parameter1;
private String parameter2;
//getters and setters
URl should be like below
/books?parameter1=blah
Like this way u can use it for both GET and POST
I have this simple controller from ajax request. It works but I wanted to return many stuff, not only List TestFlow.getFlow(flowName);
#RequestMapping(value = "/execute-flow/getFlow" , method = RequestMethod.POST)
public #ResponseBody List<String> getFlow(#RequestParam("flowName") String flowName) {
return TestFlow.getFlow(flowName);
}
Can I return multiple things to the ajax post?
For example:
#RequestMapping(value = "/execute-flow/getFlow" , method = RequestMethod.POST)
public #ResponseBody List<String> getFlow(#RequestParam("flowName") String flowName) {
return TestFlow.getFlow(flowName);
return TestFlow.getInputs(flowName);
return TestFlow.getCode(flowName);
}
Not sure what exactly you're after, but
return Arrays.asList(
TestFlow.getFlow(flowName),
TestFlow.getInputs(flowName),
TestFlow.getCode(flowName));
should work, provided that all of these are of the same type (String).
I want to create a REST-GET controller in spring-mvc that takes a list of objects, eg 10 ids as follows:
#RestController
public class MyRest {
#RequestMapping(method = RequestMethod.GET)
public Object test(#RequestParam value="id" required=false) List<Integer> ids) {
Sysout(ids);
}
}
Anyway when I call it, I have to repeat the id param multiple times:
localhost:8080/app?id=1&id=2&id=3&...
It is possible to change the param to some kind of list? Eg
app?id=1,2,3,4,5
And if yes, is this advisable? What's better from the client point of view?
Its better to use POST message with JSON or XML as request body.
As you never know how many id's will be passed.
#RestController
public class MyRest {
#RequestMapping(method = RequestMethod.POST)
public Object test(#RequestBody IDRequest request) {
Sysout(ids);
}
public static final class IDRequest {
List<Integer> ids;
<!-- getter/setters--->
}
}
where the request will be some kind of a JSON or XML like this
{"ids":[1,2,3,4,5,6,7,8,9]}
You can provide list of objects to rest service as request param.Here is the example
#RequestMapping(value = "/animals, method = RequestMethod.GET)
public void test(#RequestParam(value="animalsNames[]") String[] animalsNames) {
Sysout(animalsNames);
}
And your request looks like
http://localhost:8080/appname/animals?animalsNames[]=dog,horse
HTTP Method type : GET
Controller :
public #ResponseBody String getInfos(HttpServletRequest request,
#RequestParam #DateTimeFormat( #RequestParam List<Long> ids) {...}
Request :
http://localhost:8080/test/api?ids=1,2,3
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="")