How to delegate to a RestController by condition? - java

I have several existing #RestControllers. The path to access these controllers are eg:
localhost/first/test
localhost/second/test
code:
#RestController
#RequestMapping("/first")
public class MyRestController1 {
#ResponseStatus(HttpStatus.OK)
#RequestMapping(value = "/test", method = RequestMethod.GET)
public void test(#Valid RestParameters p) {
//...
}
}
#RestController
#RequestMapping("/second")
public class MyRestController2 {
}
Question: is it possible to catch a different url, and delegate to these controllers including automated validation of the #Valid rest parameters?
Example: localhost?param=first. Would it be possible to delegate this to localhost/first/test?
Also I want to copy the full querystring and send it to the appropriate restcontroller. The querystring will be different when accessing /first or /second, and may have different parameters.

Step 1)
Create a class which captures #Path("/"). Lets call this "Class1"
Step 2)
Create a method within Class1, but don't change the #Path("") for this method. Ensure that it appropriately captures the #QueryString("param") String firstOrLast. Lets call this "Method1"
Step 3)
Now when you access localhost?param=first, Class1.Method1() is invoked. Therefore inside this method you can build some logic (e.g. if (firstOrLast.equals("first")) { // call method_x } where method_x is the relevant delegated method as you've called it.

Related

Is it possible to map a nested mapping in spring-mvc to a global path?

I have something like this:
#RestController
#RequestMapping("/{id}")
public class MyController {
#GetMapping
public String get(#PathVariable String id) {
...
}
#PostMapping
public String post(#PathVariable String id, Payload payload) {
...
}
#GetMapping("/deeper/{id}")
public String getDeeper(#PathVariable String id) {
....
}
}
This gives 3 mappings:
/{id} (GET)
/{id} (POST)
/{id}/deeper/{id} (GET)
I would like the third of them to be just /deeper/{id} (GET).
Is it possible to do this leaving the method in the same controller and leaving that controller-wise #RequestMapping annotation?
What you request is not possible because you cannot avoid a requestMapping on a class level which makes no sense because being on class level means that you want that path to affect to all your methods.
Keep in mind that a RestController is RESTful and a class level requestMapping is used to avoid adding the same resource path to every method, so it does not make sense to have a method that can't fit in within that resource (you should move it to another controller instead).
This being said, there are a few things you can try:
1 This is not recommended. Use more than one path value on your class #ResquestMapping, in your case:
#RestController
#RequestMapping("/{id}", "/")
public class MyController{...}
You can kinda achieve what you want with this but this is extremely
discouraged and a code smell because basically means that all your methods will accept url paths either starting with id or with /, think carefully if you want to use this approach.
2 The recommended one, Remove the #RequestMapping in the class level and just update the path on every method, in your case:
#RestController
public class MyController {
#GetMapping(value = /{id})
public String get(#PathVariable String id) {
...
}
#PostMapping(value = "/{id}")
public String post(#PathVariable String id, Payload payload) {
...
}
#GetMapping("/deeper/{id}")
public String getDeeper(#PathVariable String id) {
....
}
}
3 also a recommended one Just move the method that does not fit in your controller "general logic" to another controller class which make's sense since that method is not affected by the controller general logic.

Spring restful API, is there a method being used like router to get other method's end points or URL?

#RequestMapping("/accounts")
public class controller {
#GetMapping("/get/{id}")
public final ResponseEntity<?> getHandler(){
}
#PostMapping(value = "/create")
public final ResponseEntity<?> createHandler(){
/*
trying to use some spring library methods to get the url string of
'/accounts/get/{id}' instead of manually hard coding it
*/
}
}
This is the mock code, now I am in createHandler, after finishing creating something, then I want to return a header including an URL string, but I don't want to manually concat this URL string ('/accounts/get/{id}') which is the end point of method getHandler(), so I am wondering if there is a method to use to achieve that? I know request.getRequestURI(), but that is only for the URI in the current context.
More explanation: if there is some library or framework with the implementation of route:
Routes.Accounts.get(1234)
which return the URL for the accounts get
/api/accounts/1234
The idea is, that you don't need to specify get or create (verbs are a big no-no in REST).
Imagine this:
#RequestMapping("/accounts")
public class controller {
#GetMapping("/{id}")
public final ResponseEntity<?> getHandler(#PathVariable("id") String id) {
//just to illustrate
return complicatedHandlerCalculation(id).asResponse();
}
#PostMapping
public final ResponseEntity<?> createHandler() {
//return a 204 Response, containing the URI from getHandler, with {id} resolved to the id from your database (or wherever).
}
}
This would be accessible like HTTP-GET: /api/accounts/1 and HTTP-POST: /api/accounts, the latter would return an URI for /api/accounts/2 (what can be gotten with HTTP-GET or updated/modified with HTTP-PUT)
To resolve this URI, you could use reflection and evaluate the annotations on the corresponding class/methods like Jersey does.
A Spring equivalent could be:
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
and
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(GetMapping.class).value()[0];
taken from How do i get the requestmapping value in the controller?

Call method of Controller1 into another method of Controller2 in Spring

I did Google a lot to find my problem but I couldn't and sorry If this question already on the stack overflow because I have not find it.
First let take a look into the code
#Controller
public class Controller1 {
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView methodHandler(Parameters) {
}
public int calculation(int i){
//Some Calcucation
return i;
}
}
and second controller is
#Controller
public class Controller2 {
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView methodHandler(Parameters) {
//In this I want to call the calculation(1) method of controller1.
}
}
My question is that is there any way to call the method of calculation() of controler1 in to controller2. But remember I don't want to make method static in controller1.Is there anyway to call it without make it static?
Thanks
Yasir
You should create service bean for example in configuration file (or use # one of the annotaions) and inject it into controller. For example ()
#Configuration
public class MyConfig {
#Bean
public MyService myService(){
return new MyService();
}
}
#Controller
public class Controller1 {
#Autowire
private MyService myService;
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView First(Parameters) {
myService.calculation();
}
}
#Controller
public class Controller2 {
#Autowire
private MyBean myBean;
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView First(Parameters) {
myService.calculation();
}
}
Your controllers should not call each other. If there is a logic which needs to be used by both controllers, it is much better to put that into separate bean, which will be used by both controllers. Then you can simply inject that bean to whicheveer controller neccessary. Try not to put any business logic to controllers, try tu put it to specialized class instead which will be web independent if possible and will accept web agnostic business data as user email, account number etc. No http request or response. This way your class with actual logic is reusable and can be unit tested much more easily. Also, if there is state, it should be contained in your classes outside controllers. Controllers should be stateless and not contail any state at all.
When using MVC pattern and you are deciding where to put your logic, you should separate business logic into model and into controllers you should put only logic regarding user interaction, as explained in this stack overflow post.

Automatically add "headers" and "produces" attributes to my #RequestMapping

I implemented a REST API via Spring MVC. Here is an example of a mapping:
#RequestMapping(value = "/videos", method = RequestMethod.GET, headers = "Accept=application/json", produces = "application/json")
There are many of them, so I wonder if it's possible to factorize the headers and produces attributes, so that I don't have to specify them in each mapping, in order to lighten my code?
The best would be a custom annotation which automatically sets the two attributes, for example:
#JsonRequestMapping(value = "/videos", method = RequestMethod.GET)
But I haven't been able to implement such one...
You can put #RequestMapping also on a class next to a method (see reference guide). If you want globally available attributes put a #RequestMapping on a class, this will be merged with the one on the method.
#Controller
#RequestMapping(headers = "Accept=application/json", produces = "application/json")
public class YourController { ... }
Then your method only contains the method and url.
#RequestMapping(value="/videos", method=RequestMethod.GET)
public Object someMethod(...) { ... }
You also might want to take a look at #RestController as that also configures some defaults for your controller. Like not needing a #ResponseBody anymore on your methods.
#RestController
public class YourController { ... }

Spring MVC session Attribute set intially

I have used the Spring MVC. I set the Session Attribute value like
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String initHome(Model model) {
if (!model.containsAttribute("clientObject")) {
model.addAttribute("clientObject", createDefaultClient());
}
return "homeMenu";
}
It is working fine if i click the home menu url(/home). but if i did not go the
home means it says error as 'session attribute clientObject is required'
so i decided to set sessionattibutes in constructor of controller
#Autowired
public MyController(Model model) {
if (!model.containsAttribute("clientObject")) {
model.addAttribute("clientObject", createDefaultClient());
}
}
it also says error
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myController'
I tried to set using the RequestMapping also like
#RequestMapping(value = "/", method = RequestMethod.GET)
public void initController(Model model) {
if (!model.containsAttribute("clientObject")) {
model.addAttribute("clientObject", createDefaultClient());
}
}
this method is also not called intially
my cointroller look like
#RequestMapping("/sample")
public class MyController {
..
..
is it possible to set the sessionAttribute value in the constructor of controller? or any other way to set the session Attribute initially?
Thanks in advance for your help.
Assuming your createDefaultClient is in the controller add a #ModelAttribute annotation to it.
#ModelAttribute("clientObject")
public ClientObject createDefaultClient() { ... }
This method will be called before any request handling method (as explained in the reference guide)
If you combine that with a #SessionAttribute annotation on your class (which you might already have). You should be able to achieve what you want.
In your request handling methods (methods annotated with #RequestMapping) you can now simply inject the client object as a method argument.
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String initHome(#ModelAttribute("clientObject") ClientObject clientObject) {
// Do something with the clientObject
return "homeMenu";
}
This will only work consistenly within the same controller, so if you need the ClientObject to be used somewhere else (another controller for instance), this isn't going to work (nor is #SessionAttributes designed for that).

Categories