Switch endpoint responses based on condition - java

I would like to have two endpoints with the same path and decide which one is enabled on startup.
To do so I've tried using #ConditionalOnExpression() but an error is thrown as it says a mapping already exists. One endpoint uses ModelAndView while the other provides a html string so I can't add an if statement in the body of the endpoint.
Code
#ConditionalOnExpression("${my-property:false}")
#GetMapping(VIEW_INDEX)
public ModelAndView index(HttpServletRequest request) {
...
return modelAndView;
}
#ConditionalOnProperty("${my-property}")
#GetMapping(VIEW_INDEX)
public String otherIndex(){
return "/other/index";
}
Error
Ambiguous mapping. Cannot map 'controller' method
There is already 'controller' bean method
How can I allow only one to be enabled based on a condition without there being an Ambiguous mapping?

It will work if you try #ConditionalOnProperty or ConditionalOnExpression on 2 controllers with the same request mapping.
#RestController
#RequestMapping("/test")
#ConditionalOnProperty(value = "my-property", havingValue = "true")
public class TestTrueController {
#GetMapping("/index")
public String index() {
return "Forever true!";
}
}
#RestController
#RequestMapping("/test")
#ConditionalOnProperty(value = "my-property", havingValue = "false")
public class TestFalseController {
#GetMapping("/index")
public String index() {
return "Forever false!";
}
}
If your my-property value is true it will print "Forever true!", if false it will print "Forever false!".

Related

Log url to Spring controller from inside it

Suppose you have a Spring MVC controller, something like this
#Controller
public class RestController {
#GetMapping(value = "/test")
public #ResponseBody Test getTestData(...) {
// console log path to controller: http://localhost:80/app/test
return testData;
}
}
Is it possible to log/print from inside the controller the url to it? In the example above the output would be something like https://localhost:80/app/test
Using .getRequestUrl from the servlet is not behaving correctly.
You can inject UriComponentsBuilder as parameter then use the method toUriString(). From the documentation, it is used to build a relative URI from the current request’s, this should work as your are expected Doc.
#Controller
public class RestController {
...
#GetMapping(value = "/test")
public #ResponseBody Test getTestData(UriComponentsBuilder ucb, ...) {
LOGGER.debug(ucb.toUriString());
// console log path to controller: http://localhost:80/app/test
return testData;
}
...
}

Difference between path and value attributes in #RequestMapping annotation

What is the difference between below two attributes and which one to use when?
#GetMapping(path = "/usr/{userId}")
public String findDBUserGetMapping(#PathVariable("userId") String userId) {
return "Test User";
}
#RequestMapping(value = "/usr/{userId}", method = RequestMethod.GET)
public String findDBUserReqMapping(#PathVariable("userId") String userId) {
return "Test User";
}
As mentioned in the comments (and the documentation), value is an alias to path. Spring often declares the value element as an alias to a commonly used element. In the case of #RequestMapping (and #GetMapping, ...) this is the path property:
This is an alias for path(). For example #RequestMapping("/foo") is equivalent to #RequestMapping(path="/foo").
The reasoning behind this is that the value element is the default when it comes to annotations, so it allows you to write code in a more concise way.
Other examples of this are:
#RequestParam (value → name)
#PathVariable (value → name)
...
However, aliases aren't limited to annotation elements only, because as you demonstrated in your example, #GetMapping is an alias for #RequestMapping(method = RequestMethod.GET).
Just looking for references of AliasFor in their code allows you to see that they do this quite often.
#GetMapping is a shorthand for #RequestMapping(method = RequestMethod.GET).
In your case.
#GetMapping(path = "/usr/{userId}") is a shorthand for #RequestMapping(value = "/usr/{userId}", method = RequestMethod.GET).
Both are equivalent. Prefer using shorthand #GetMapping over the more verbose alternative. One thing that you can do with #RequestMapping which you can't with #GetMapping is to provide multiple request methods.
#RequestMapping(value = "/path", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT)
public void handleRequet() {
}
Use #RequestMapping when you need to provide multiple Http verbs.
Another usage of #RequestMapping is when you need to provide a top level path for a controller. For e.g.
#RestController
#RequestMapping("/users")
public class UserController {
#PostMapping
public void createUser(Request request) {
// POST /users
// create a user
}
#GetMapping
public Users getUsers(Request request) {
// GET /users
// get users
}
#GetMapping("/{id}")
public Users getUserById(#PathVariable long id) {
// GET /users/1
// get user by id
}
}
#GetMapping is an alias for #RequestMapping
#GetMapping is a composed annotation that acts as a shortcut for #RequestMapping(method = RequestMethod.GET).
value method is an alias for path method.
This is an alias for path(). For example #RequestMapping("/foo") is equivalent to #RequestMapping(path="/foo").
So both methods are similar in that sense.

Swagger Controller Mappings

With Swagger-UI I am trying to display methods that have the same base URL underneath one Swagger container regardless of which controller they fall under. Imagine this code.
// Current Swagger Config Class
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(getPaths())
.build()
.apiInfo(getApiInfo());
}
private Predicate<String> getPaths(){
return or(regex("/attributes.*"),
regex("/other.*"));
}
// Controller 1
#RestController
#RequestMapping("/attributes")
#Api(value = "User Attribute Services", description = "User Attributes API")
public class UserAttributesController {
#Autowired
private UserAttributesService userService;
#Autowired
private UserAttributeValidator userAttributeValidator;
#RequestMapping(value = "/user/search", method = RequestMethod.POST, produces = "application/json")
public List<User> searchUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/insert", method = RequestMethod.POST)
public boolean insertUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/update{id}", method = RequestMethod.PATCH)
public boolean updateUser(#PathVariable id, #RequestBody UserDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/user/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteUser(#PathVariable id){
// meaningless implementation ...
}
}
// Controller 2
#RestController
#RequestMapping("/attributes")
#Api(value = "Tech Attribute Services", description = "Tech Attributes API")
public class TechAttributesController {
#Autowired
private TechAttributesService techService;
#Autowired
private TechAttributeValidator techAttributeValidator;
#RequestMapping(value = "/tech/search", method = RequestMethod.POST, produces = "application/json")
public List<Tech> searchTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/insert", method = RequestMethod.POST)
public boolean insertTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/update{id}", method = RequestMethod.PATCH)
public boolean updateTech(#PathVariable id, #RequestBody TechDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteTech(#PathVariable id){
// meaningless implementation ...
}
}
My application swagger page is displaying the attributes underneath seperate containers. Eventhough I specified the regex path of '/attributes' in my Swagger Configuration the fact that these are in different controllers is overriding what I want. If I put all these methods into 1 controller I get the desired result, but this is only a subset of the attribute classes I have and I do not want a massive controller that spans over 1k lines.
Extra: If you noticed the services are almost identical for the attributes. I have been trying to find a clever way of declaring them once and simply overriding the method declations instead of redefining similiar services over again, but because the #RequestMapping annotation needs to be a constant value I could not find a way to change the annotation depending on the implementation rather than the declaration.
Swagger-UI Picture

Difference between two #RequestMapping annotations

I am pretty new in Spring MVC and I have the following doubt.
In a controller, I have a method annotated in this way:
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
So this method handle HTTP Request toward the URL /users?register where register is a parameter (because the entire class handle request toward /users resource).
Is it the same thing if, instead using the params = "register" I use the following syntaxt:
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
I have deleted the mapping at class level and I use #RequestMapping("/users/{register}").
Is it the same meaning of the first example?
NO, they are completely different constructs:
Code 1
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, createForm method will be called when a HTTP request is made at URL /users?register. Quoting from Spring Javadoc, it means this method will be called whatever the value of the register HTTP parameter; it just has to be present.
"myParam" style expressions are also supported, with such parameters having to be present in the request (allowed to have any value).
Code 2
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, #RequestMapping is declaring register as a PathVariable. The method createForm will be called if a HTTP request is made at URL /users/something, whatever the something. And you can actually retrieve this something like this:
#RequestMapping("/users/{register}")
public String createForm(#PathVariable("register") String register, Model model) {
// here "register" will have value "something".
model.addAttribute("user", new Customer());
return "user/register";
}

#Path equivalent in spring-boot

Say I have controller with a method defined like this:
#RestController
#RequestMapping("/{user-id}/foo")
public class FooController {
#RequestMapping("bar")
public BarController someBarLogic() {
//
}
}
Is it possible to go further than {user-id}/foo/bar without specifying the whole root-path ? (Is there a way to relativize the path like #Path annotation in Jersey or an equivalent annotation in Spring-Boot ?)
It looks like you're looking for ant-style wildcards in path patterns. Have a look here:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-patterns
Accordingly you can define a RequestMapping like this
#RestController
#RequestMapping("/foo")
public class DemoController {
#RequestMapping(value = "/bar/**", method = RequestMethod.GET)
public String getDemo() {
return "Hello world!";
}
}
which will match /foo/bar/baz.
Ok, another example based on your comment below:
#RestController
public class DemoController {
#RequestMapping(value = "/**/baz/**", method = RequestMethod.GET)
public String getDemo() {
return "Hello world!";
}
}
This will match the same url as above, and also /foo/bar/baz/bar/foo

Categories