How to map a path to multiple controllers? - spring

I'm currently working on a spring based web application and have a special requirement that seems not (at least not out of the box) be provided by spring MVC. The application serves data for multiple users each organized in their own "company". Once a user has logged in, I'm able to identify to which company he belongs to.
The application itself is built with multiple "modules", each with it's own domain objects, DAO, Service and Controller classes. The idea behind this concept is that I can for example extend a certain controller class (let's say to use a different service class) based upon the user and here is my problem.
Since i do not want to change my request paths for certain users, I'm currently looking for a way how to serve a request issued on a certain request path with different instances of a controller based upon the user issuing the request.
I came up with the idea to attach a HTTP Header Field for the company
Example:
X-Company:12345
and have my controllers configured like this:
#Controller
#RequestMapping(value="/foo/")
public class FooController {
// ...
}
#Controller
#RequestMapping(value="/foo" headers="X-Company=12345")
public class SpecialFooController extends FooController {
// ...
}
However this is not possible, since spring MVC treats each header (except Content-Type and Accept) as a kind of restriction, so in my case it would handle all requests with the FooController instead of the SpecialFooController unless i add a "headers" restriction on the FooController as well, which is not practicable.
Is there some way how to customize this behaviour or some direction one could point me to look for? Or maybe someone has another idea how to achieve this. It'll be highly appreciated.
Thanks!

I'am not sure but I think you can do this with HandlerMapping. Have a look at the documentation

To take your own suggestion, you can use the #RequestHeader annotation in your controller methods:
#Controller
public class MyController {
#RequestMapping("/someAction")
public void myControllerMethod(#RequestHeader('X-Company-Id') String companyId) {
}
}
Or you could use #PathVariable:
#Controller
public class MyController {
#RequestMapping("/{companyId}/someAction")
public void myControllerMethod(#PathVariable("companyId") String companyId) {
}
}
Using this approach would mean that it is in fact different URLs for each company, but if you can set the company id header, I guess you also can suffix the URLs with the company id.
But there are also other possibilities. You could write an interceptor that puts the company id in a session or request variable. Then you wouldn't have to add the annotation to every controller method. You could also use a subdomain for each company, but that wouldn't look too pretty if the company id is a random alphanumeric string. E.g: companyone.mydomain.com, companytwo.mydomain.com
Edit
#RequestMapping can be added to the controller level as you know, so you should be able to do
#Controller
#RequestMapping("/controller/{companyId}")
as the base url, if that's a better option.

I was able to meet the requirement by making usage of a customized RequestCondition. By defining your own annotation that can be placed at the type and method level of a controller. Extending the RequestMappingHandlerMapping by your own implementation and overriding the getCustomTypeCondition() and getCustomMethodCondition() methods translates a controller annotation into your own RequestCondition.
When a request comes in, the custom RequestCondition will be evaluated and the annotated controller(method) will then be called to serve the request. However this has the downside, that one needs to remove a servlet-context.xml file and switch to the WebMvcConfigurationSupport class instead in order to be able to use your customized RequestMappingHandlerMapping class.
This question was also discussed here.
Edit:
A pretty good example using this can be found here.

Related

Spring RequestMapping Controller annotation and create a different absolute path inside the same Controller

From the perpective of Restful Apis, its said its a good choice to design them hierarchical when your database is hierarchical too, above all because the client learns and knows the hierarchical structure of the entities. I mean, for instance, if you have bank clients and accounts, the parent entity would be the clients and the child entities would be the accounts. So :
To get the accounts from the client 1, a right URI could be something like "/clients/1/accounts"
From the perspective of Spring controllers, I should have a ClientController and a AccountController but
The AccountController should process the above request, right?
Could I specify and URI like "accounts/?clientId=1"? Its a bad design?
If I go with the option 1, how to specify this URI in the AccountsController?? If not, should I create another controller just for this and not put this URI in the Account controller?
#RequestMapping("/clients")
public class ClientsController{ }
#RequestMapping("/accounts")
public class AccountsController{
#RequestMapping("/clients/{idClient}/accounts") => **I cannot specify
an absolute path here because
its relative to the RequestMapping annotation in the Controller**
#GetMapping
public #ResponseBody List<Account> getAccounts(){}
}
Thanks!!
There's no hard bound rules it's matter of choice and use cases that decides how we structure our rest uris and using query param or path variables is for constructing meaningful and readable uris.
Suppose if you have.usecase is like to get the list of accounts needs a client ID as mandatory then construct:
GET /clients/{id}/accounts
And put it in clients controller.
But if your usecase is like client id is not mandatory to get list of accounts then construct:
GET /accounts?clientid=1
And put it in accounts controller. Now you can choose to make clientid as required=false request param.
Don't construct deeply nested APIs.. make dumb endpoints eventually you'll end up facing usecases where you'll need to create non nested uris.

RequestMapping discriminating by user in Spring

I have two controllers that handle the same resource. One for the regular web app and another one for portals (and in theory there could be more than one portal and more controllers).
#Controller
#RequestMapping(value = "/*/web/cases")
public class CasesDetailController extends BaseController<CasesForm> { ... }
#Controller
#RequestMapping(value = "/*/portal/cases")
public class CasesPortalDetailController extends BaseController<CasesForm> { ... }
In some controllers we just resolve a different view for the portal, but for some cases the controller logic is quite different. For example this one has many mappings in the portal that are not available in the app.
The problem comes with managing 2 different urls for the same resource. For example, if I wanna place a link for a case I have to calculate it in the server by checking if the current user is logged in the web or the portal instead of placing a constant base url and an uuid as parameter.
My question is if I can do something about the request mapping to decide which controller can I use. The best I can think of is using a parameter:
#RequestMapping(value = "/*/cases") // Web
#RequestMapping(value = "/*/cases", params = "device=portal") // Portal
But I would like to avoid sending that info in the url when I have it available in the user (we use a custom user class that extends the base user from spring and it contains the device). Is there any way to check that in the request mapping? Something like this:
#RequestMapping(value = "/*/cases", magicFunctionality=getLoggedUser().getDevice()="web")
Probably nothing like this exists (at least I found nothing in the manual) but any ideas of how to face this problem would be wellcome.
Thanks in advance.
Gonzalo.

Request body field processing in spring

I am working on a spring base web application where, we have a few RestControllers and some Request DTO classes. Request DTO contains a token field which needs some validation. So I used spring validators to validate that. After validation, we want to send that field to an external system using another REST API (Just for some kind of analytics logging). The same field is repeated in multiple DTO objects and their controllers. So, I am easily able to define annotations for validators and reuse them across the DTOs. But I am not sure how to process that field after validation succeeds (i.e. call analytics API to consume that field post validation), without mixing it with the core logic of controllers.
Approaches I could think of:
Implement a filter/interceptor and process the field there. But then
there is a limitation that request body can be read only once so I
need to use some alternate ways by creating request wrappers.
Repeat the logic in every controller and it is very error prone as for
every new controller we need to remember to write that code.
But non of these approaches look cleaner. Can someone recommend a better way to achieve that?
Thanks in advance.
You can create a BaseController and implement the method there. Extend this BaseController wherever you need this logging service. Like below.
BaseController.java
class BaseController {
protected void remoteLogging(String name,String token) {
//Calling the remote log services}
}
AppController.java
#Controller
#RequestMapping("register")
public class LeaseController extends BaseController {
#PostMapping("new")
public String new(#Valid #ModelAttribute("registration") Registration registration,BindingResult result){
if(rest.hasErrors(){
remoteLogging("name","token");
}
}

How can I map URL containing colon to my Spring controller?

I want to map a URL (for example, http://example.com/v1/books:search) containing colons to my Spring MVC controller, but I can't make it work.
#RequestMapping("/v1/books")
public class BooksController {
#GetMapping(":search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
When I test this API, Spring returns 404 NOT_FOUND to me. It seems that Spring doesn't support colons in URL mapping.
Is there any method to make it work? Thanks.
I hit this attempting to do similar so I thought I'd share my findings.
With using most defaults and your code, the search method will be mapped to /v1/books/:search which is obviously not quite what you want. There are two places that I've found so far that get in the way of changing this. The first is the AntPathMatcher's combine method. This method will attempt to put a path separator (/) between segments. The second place is within the RequestMappingInfo's path parsing code. The former can be replaced easily. The latter not so much.
As the methods that tend to be problematic involve combining multiple #RequestMapping annotations, what I've found to work is to simply side-step combinations. On my controller class, I have a #Controller annotation and any defaults for #RequestMapping, but not a path attribute. On each method, the full path is then added. This isn't great, but it does get collection-level special "methods" to function properly. In your example, this would look like:
#Controller
#RequestMapping
public class BooksController {
#GetMapping("/v1/books:search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
Long story short: Do not do this - use / as a separator for the method.
A bit more detail: Have a look at Spring Framework issue #24771 that suggests that the team actually moves away from various ways to handle non-standard URL mappings in favor of simpler logic of URL processing, after entangling in a series of various issues with similar concepts. This "custom method" thing is unlikely to get a first class support in Spring, as a result.
Therefore, despite what Google does, just do this as a normal person and use /v1/books/search path:
#RequestMapping("v1/books")
public class BooksController {
#GetMapping("search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
}

Spring: controller inheritance using #Controller annotation

I'd like to be able to create a base controller in my Spring app that, among other things, determines if a user is a registered user or not. This base controller, following the template design pattern, would contain an abstract protected method that controller subclasses would implement.
The abstract method would have passed to it an instance of User, registered or otherwise. However, I have no idea how I would do this since it seems that by using controllers purely using the #Controller annotation each controller is free to define their request handling method however they like.
Would creating some sort of user service class that is injected into each controller and used to validate a user be one way to get around this? This begs the question (at least for me) how does such a controller get a hold of a HttpServletRequest or the Session object?
Thanks.
Define an abstract BaseController, with no annotations
Define concrete and abstract methods
Call these methods from subclasses (which are annotated with #Controller) whenever needed.
I think the Base Controller is not a good idea if the only code it is to have is for UserAuthentication...instead use Spring security. This is the best option.
Alternatively, you can have methods like this...take a look at the Spring reference..
#Controller("loginController")
public class LoginController {
#RequestMapping(value="/login.do", method=RequestMethod.POST)
public String login(Model model, HttpServletRequest request) {
String userIdFromRequest = (String)request.getParameter("userId");
String password = (String)request.getParameter("password");
boolean verified = ...send userIdFromRequest and password to the user service for
verification...
if (verified){
request.getSession().setAttribute("userId", userIdFromRequest);
}
}
//More Methods
}
Did it help?
-SB
The basic problem is that annotational bootstrapping is not polymorphic. I found this paper useful: http://sanguinecomputing.com/design-pattern-for-hierarchical-controller-organization-with-annotational-configuration-spring-mvc-3/

Resources