Spring REST multiple controllers for one URL but different http methods - spring

I currently have one controller that handles both GET and POST for URL groups:
#Controller
public class RestGroups {
...
#RequestMapping(method = RequestMethod.GET, value = "/groups")
#ResponseBody
public GroupsDto groups() {
return new GroupsDto(getGroups());
}
#RequestMapping(method = RequestMethod.POST, value = "/groups", headers = "Accept=application/xml")
#ResponseBody
public GroupsDto postGroup(#RequestBody GroupDto groupDto) {
groupSaver.save(groupDto.createEntity());
return groups();
}
Now I would like to have TWO controllers, both assigned for same URL but each for different method, something like below:
#Controller
public class GetGroups {
...
#RequestMapping(method = RequestMethod.GET, value = "/groups")
#ResponseBody
public GroupsDto groups() {
return new GroupsDto(getGroups());
}
...
}
#Controller
public class PostGroup {
...
#RequestMapping(method = RequestMethod.POST, value = "/groups", headers = "Accept=application/xml")
#ResponseBody
public GroupsDto postGroup(#RequestBody GroupDto groupDto) {
groupSaver.save(groupDto.createEntity());
return groups();
}
...
}
Is it possible? Because now I get Spring exception that one URL cannot be handled by two different controllers. Is there a workaround for this issue? I really would like to separate those two completely different actions into two separate classes.

This limitation has been solved in Spring 3.1 with its new HandlerMethod abstraction. You'll have to upgrade to 3.1.M2. Let me know if you need an example.

Related

Request Mapping Configuration set globally in spring boot

I have a request mapping in every controller like below, now I want to set this configuration from one place of my applications
Here is my code:
#RestController(value = "AC1004Controller")
#RequestMapping(value = { "api/v1/accounting"},method = RequestMethod.POST ,consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public class AC1004Controller {
}
My target coding is, need to replace the below code from one place of our application
#RequestMapping(value = { "api/v1/accounting"},method = RequestMethod.POST ,consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE})
Generally you map controller's methods with GET, POST, etc ..
so below should be configuration..
Define a property in application.properties
api.endpoint.accounting=/api/v1/accounting
Below controller should mapped with your accounting controller with different-2 methods for post, get to mapped with controller method.
#RestController(value = "AC1004Controller")
#RequestMapping(value = "${api.endpoint.accounting}")
public class AC1004Controller {
#PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public ResponseEntity<?> addAccount(#RequestBody Account account) {
}
//for get mapping
#GetMapping
public ResponseEntity<?> getAccount() {
}
}
You need to set spring.mvc.servlet.path property in application.properties file.
Like this:
spring.mvc.servlet.path=/AC1004Controller
You just put any of these configurations on application properties file (yaml or properties).
spring.data.rest.basePath=/api
spring.data.rest.base-path=/api

Create generic method to forward to index.html requestes which throws 404

I Spring Boot app combined with Angular 4. I created some routes but when I type into url some string like localhost:8080/items I get error 404 not found. So that's why I created forwarding when it detected if it the url looks like localhost:8080/items it forwards to index.html. But what about a case when I have multiple possible urls to typed and I don't want to rewirte it again and again...?
#RequestMapping(value = "/api/user", method = RequestMethod.POST)
#RequestMapping(value = "/api/user", method = RequestMethod.GET)
#RequestMapping(value = "/api/item", method = RequestMethod.GET)
#RequestMapping(value = "/api/item", method = RequestMethod.POST)
.
.
.
and so on
I wanted to create something generic like:
#Controller
public class ViewController {
#RequestMapping({ "/**" })
public String index() {
return "forward:/index.html";
}
}
but it doesn't work at all. Or there is another way to make it work?
The proper correction of this case is:
In app.module add
{ path: '**', component: ItemListComponent}
which indicated what should you do when something goes wrong and add a specific java controller :
#Controller
public class ViewController {
#RequestMapping(value = "{path:[^\\.]*}")
public String index() {
return "forward:/index.html";
}
}

Spring MVC RestController allow params with different names in methods

I am writing an API using Spring MVC and I am coming up with a problem allowing apps written in different languages to consume my API.
It turns out that the "Ruby users" like to have their params named in snake_case and our "Java users" like to have their param names in camel_case.
Is it possible to create my methods that allow param names to be named multiple ways, but mapped to the same method variable?
For instance... If I have a method that accepts a number of variables, of them there is mapped to a postal code. Could I write my method with a #RequestParam that accepts BOTH "postal_code" and "postalCode" and maps it to the same variable?
Neither JAX-RS #QueryParam nor Spring #RequestParam support your requirement i.e., mapping multiple request parameter names to the same variable.
I recommend not to do this as it will be very hard to support because of the confusion like which parameter is coming from which client.
But if you really wanted to handle this ((because you can't change the URL coming from 3rd parties, agreed long back), then the alternative is to make use of HandlerMethodArgumentResolver which helps in passing our own request argument (like #MyRequestParam) to the controller method like as shown in the below code:
Controller class:
#Controller
public class MyController {
#RequestMapping(value="/xyz")
public void train1(#MyRequestParam String postcode) {//custom method argument injected
//Add your code here
}
}
MyRequestParam :
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface MyRequestParam {
}
HandlerMethodArgumentResolver Impl class:
public class MyRequestParamWebArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
MyRequestParam myRequestParam =
parameter.getParameterAnnotation(MyRequestParam.class);
if(myRequestParam != null) {
HttpServletRequest request =
(HttpServletRequest) webRequest.getNativeRequest();
String myParamValueToBeSentToController = "";
//set the value from request.getParameter("postal_code")
//or request.getParameter("postalCode")
return myParamValueToBeSentToController;
}
return null;
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.getParameterAnnotation(MyRequestParam.class) != null);
}
}
WebMvcConfigurerAdapter class:
#Configuration
class WebMvcContext extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new MyRequestParamWebArgumentResolver());
}
}
I think what you want to do is not allowed by Spring framework with the annotation RequestParam.
But if you can change the code or say to your third party to modify the calls i would suggest you 2 options
Option 1:
Use the #PathVariable property
#RequestMapping(value = "/postalcode/{postalCode}", method = RequestMethod.GET)
public ModelAndView yourMethod(#PathVariable("postalCode") String postalCode) {
//...your code
Here does not matter if the are calling your URL as:
http://domain/app/postalcode/E1-2ES
http://domain/app/postalcode/23580
Option 2:
Create 2 methods in your controller and use the same service
#RequestMapping(value = "/postalcode", method = RequestMethod.GET, params={"postalCode"})
public ModelAndView yourMethod(#RequestParam("postalCode") String postalCode) {
//...call the service
#RequestMapping(value = "/postalcode", method = RequestMethod.GET, params={"postal_code"})
public ModelAndView yourMethodClient2(#RequestParam("postal_code") String postalCode) {
//...call the service
If is possible, I would suggest you option 1 is much more scalable

Which method gets called for below request mapping?

Among the below two methods, which one gets called first?
#RequestMapping(method = RequestMethod.POST, params="continue")
public String save(){
}
#RequestMapping(method = RequestMethod.POST, params="continuesave")
public String saveReview(){
}
Params sent in POST request include:
continue,
continuesave="true"
In my local machine, method 1 gets called. But in our prod servers, method 2 is getting called. What is the method calling criteria?
When I try to run your example, I get an exception java.lang.IllegalStateException: Ambiguous handler methods mapped
By the way, you can change the priority of handlers by negating params(saveReview won't call for both params):
#RequestMapping(method = RequestMethod.POST, params="continue")
public String save(){
...
}
#RequestMapping(method = RequestMethod.POST, params={"continuesave"," !continue"})
public String saveReview(){
...
}
You should only map to non overlapping urls. What happens in you case is just undefined behaviour: it may depends on many things and cannot be securely predicted (it even throws an exception in #Dekart test).
Here if both params can be simultaneously present in a request you should have only one mapping and test for the parameters inside the controller method:
#RequestMapping(method = RequestMethod.POST)
public String save_req(WebRequest web) {
Map<String,String[]> param = web.getParameterMap();
if (...) { // condition for save
return save();
}
else {
saveReview();
}
}
public String save(){
}
public String saveReview(){
}

Different RequestMapping value within same Controller

There is an existing Controller that I want to add an additional get Method with a slightly modified logic. There is the findAll method and I want to add the getMessages method.
#RestController
#RequestMapping(value = "/options", produces = MediaType.APPLICATION_JSON_VALUE)
public class OptionController {
...Definitions etc...
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<?> findAll(#PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
Page<Option> page = optionRepository.findAll(pageable);
return ok(pagingAssembler.toResource(page));
}
}
And below the new method:
#RequestMapping(value = "/optionsWelcome", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<?> getMessages(#PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
Page<Option> page = optionRepository.findAll(pageable);
return ok(pagingAssembler.toResource(page));
}
I am getting 404 for http calls to /optionsWelcome but /options works.
Is it possible to have a controller with mappings for 2 different URLs or do I need to make a second controller?
/options is the mapping for the entire controller. /options/optionsWelcome will probably work.
You need to move /options mapping to the method.

Resources