A REST-API I am developing allows access to various kinds of user data.
Users can be identified via their Id, their email or their username. A user then has a couple of other data such as orders etc.
Now I am planning to expose the following endpoints:
/rest/user/byemail/test#example.org/profile
/rest/user/byemail/test#example.org/orders
/rest/user/byemail/test#example.org/address
/rest/user/byid/123456/profile
/rest/user/byid/123456/orders
/rest/user/byid/123456/address
/rest/user/byusername/test/profile
/rest/user/byusername/test/orders
/rest/user/byusername/test/address
As you can see, the URL always consists of two "parts": One for identifying the user and the other one for identifying the resource.
It would be great if I could now avoid writing 9 different methods in my controller (as there might be other types of information in the future).
Is it somehow possible to write one UserController which is then returned and parametrized by the MainController?
public class UserController {
#RequestMapping("/profile")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/orders")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/address")
public ModelAndView profile(User user) {
//...
}
}
#RequestMapping("/rest/user")
public class MainController {
#RequestMapping("byemail/{email}")
public ??? byEmail(#PathVariable String email) {
User user = //Find user by email
//???
}
#RequestMapping("byusername/{username}")
public ??? byUserName(#PathVariable String username) {
User user = //Find user by username
//???
}
#RequestMapping("byid/{id}")
public ??? byId(#PathVariable String id) {
User user = //Find user by id
//???
}
}
Or is it maybe possible to solve this via some kind of routing?
However, it would be important to "split" the URL and use one half to find the correct user which will then be available as a parameter when parsing the second half.
Why not just one controller with these request mappings?
#RequestMapping("/rest/user")
public class UserController {
#RequestMapping("{keyType}/{key}/orders")
public ModelAndView orders(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/profile")
public ModelAndView profile(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/address")
public ModelAndView address(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
private User findUser(String keyType, String key) {
// ...
}
}
Take a look at Spring Data REST.
This API exposes a SimpleJpaRespository to the web, accessible via RESTful paths.
Related
I have a condition where I want to use the same method to be called for request coming from mobile app and request coming from webapp with different clientId and different Client secret and redirect uris,base uris .
When a request comes from mob we have header "source" value as mobile and when request comes from web we have header "source" value as web.
I want to have something like that when request comes from web ,values of client id,clientsecret,baseurl,redirecturl of web is loaded and when request comes from mobile
values of client id,clientsecret,baseurl,redirecturl of mobile is loaded.
I want to avoid to write the same logic either by creating in different method or different endpoint for mobile/web.I wan to use the same method and the same endpoint ,just that values injected from conifguration will be different based on the header value
How can I achieve this??Is there a way to #Autowire different config class based on condition
TokenController
public class TokenController {
#GetMapping("/getToken")
public ResponseEntity<?> fetchToken(#RequestParam("foo") String foo) {
ResponseEntity<?> tokenInfo = tokenInfoServiceImpl.getTokenDetails(foo);
return tokenInfo;
}}
TokenServiceImpl
public class TokenServiceImpl{
#Autowired
SSOConfig ssoConfig;
public ResponseEntity<?> getTokenDetails(String foo) {
HttpHeaders tokenExchangeHeaders = prepareRestTemplateHeader(ssoConfig.getClientId(),
ssoConfig.getClientSecret());
String baseUrl =soConfig.getBaseurl();
String redirectUrl = ssoConfig.getRedirectUrl();
//rest of the logic
}
}
SSOConfig class
#Configuration
#ConfigurationProperties(prefix = "sso.web")
public class SSOConfig {
private String baseurl;
private String redirectUrl;
private String tokenClientId;
private String tokenClientSecret;
//Getters and Setters
}
For your TokenController class, you should be able to add an argument to process a RequestHeader.
public class TokenController {
#GetMapping("/getToken")
public ResponseEntity<?> fetchToken(#RequestHeader(name = "source") String source, #RequestParam("foo") String foo) {
ResponseEntity<?> tokenInfo = null;
if ("mobile".equals(source)) {
ResponseEntity<?> tokenInfo = tokenInfoServiceImpl.getTokenDetails(foo);
} else {
//do something else
}
return tokenInfo;
}}
There's a great tutorial on this at Baeldung
Following is a code snippet where we can use #ModelAttribute at method parameter level
#ReqestMapping(value = useruri)
public void submitInfo(#ModelAttribute User user) {
// Business logic
}
#ReqestMapping(value = personuri)
public void submitInfo(#ModelAttribute Person person) {
// Business logic
}
Can we do like following?
#RequestMapping(value = genericuri)
public void submitInfo(HttpServletRequest request, #PathVariable String type) {
if (type.equals("user")) {
User user = someSpringMvcMethod(request, User.class)
} else if (type.equals("person")) {
Person person = someSpringMvcMethod(request, Person.class)
}
//Business logic
}
Reason is, I am expecting different type of submitted data based on a type and I want to write a generic controller since only difference is conversion of request data to specific java class.
User and Person class has lot of different data and I don't think I can use inheritance/polymorphism to solve my use-case here
I don't recommend such a thing.
Look here
if (type.equals("user")) {
User user = someSpringMvcMethod(request, User.class)
} else if (type.equals("person")) {
Person person = someSpringMvcMethod(request, Person.class)
}
This is already wrong, imho. A single method managing multiple models.
What if you need another model's type? Another if branch.
For example, this is a lot better
#ReqestMapping("base-path/user")
public void submitInfo(#ModelAttribute final User user) {
commonLogic(user.valueOne, user.valueTwo);
}
#ReqestMapping("base-path/person")
public void submitInfo(#ModelAttribute final Person person) {
commonLogic(person.valueOne, person.valueTwo);
}
private void commonLogic(final String one, final String two) {
... // Business logic
}
commonLogic manages the common business logic between the models' types.
It centralizes the work.
You can even place commonLogic in a Service, which is where it should go anyway.
I'm reading https://jersey.github.io/documentation/latest/filters-and-interceptors.html and http://www.dropwizard.io/1.1.4/docs/manual/core.html#jersey-filters to try and make this:
#CookieParam("User-Data") userData: String,
#HeaderParam("User-Agent") userAgent: String,
Not needed in each and every resource GET method of my web app. userData is json data from a cookie with fields like "name" and "id" and userAgent is the full User-Agent string from the header. For each view I pass in:
AppUser.getName(userData), AppUser.isMobile(userAgent)
The getName function parses the json and returns just the name field and the isMobile function returns a true boolean if the string "mobile" is found.
I use this in each view of the app in FreeMarker to display the user's name and to change some layout stuff if mobile is true.
Is there a way to make this less repetitive? I'd rather use a BeforeFilter to just set this automatically each time.
Sounds like something you can just do in a ContainerResponseFilter, which gets called after the return of the view resource/controller. Assuming you are returning a Viewable, you get the Viewable from the ContainerRequestContext#getEntity, get the model from it, and add the extra information to the model.
#Provider
#UserInModel
public class UserInModelFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
Cookie cookie = request.getCookies().get("User-Data");
String header = request.getHeaderString("User-Agent");
String username = AppUser.getName(cookie.getValue());
boolean isMobile = AppUser.isMobile(header);
Viewable returnViewable = (Viewable) response.getEntity();
Map<String, Object> model = (Map<String, Object>) returnViewable.getModel();
model.put("username", username);
model.put("isMobile", isMobile);
}
}
The #UserInModel annotation is a custom Name Binding annotation, which is used to determine which resource classes or methods should go through this filter. Since you don't want all endpoints to go through this filter, just annotate the methods or classes you want.
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface UserInModel {
}
#Path("/")
public class IndexController {
#GET
#UserInModel
#Produces(MediaType.TEXT_HTML)
public Viewable home() {
Map<String, Object> model = new HashMap<>();
return new Viewable("/index", model);
}
}
With Dropwizard, all you need to do is register the filter.
env.jersey().register(UserInModelFilter.class);
If you want to do some preprocessing of the cookie and header before the resource method is called, you can do that in a ContainerRequestFilter, which can also be name bound. And instead of recalculating the AppUser.xxx method in the response filter, you can also just set a property on the ContainerRequestContext#setProperty that you can later retrieve from the same context (getProperty) in the response filter.
UPDATE
The above answer assumes you are using Jersey's MVC support, hence the use of Viewable. If you are using Dropwizard's view support, then it's not much different. You may want to create an abstract class as a parent for all the view classes, that way you can just cast to the abstract type when retrieving the entity from the filter.
public class AbstractView extends View {
private String userName;
private boolean isMobile;
protected AbstractView(String templateName) {
super(templateName);
}
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public boolean isMobile() { return isMobile; }
public void setIsMobile(boolean mobile) { isMobile = mobile; }
}
public class PersonView extends AbstractView {
private final Person person;
public PersonView(Person person) {
super("person.ftl");
this.person = person;
}
public Person getPerson() {
return this.person;
}
}
In the filter
#Provider
#UserInModel
public class UserInModelFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
Cookie cookie = request.getCookies().get("User-Data");
String header = request.getHeaderString("User-Agent");
String username = AppUser.getName(cookie.getValue());
boolean isMobile = AppUser.isMobile(header);
AbstractView returnViewable = (AbstractView) response.getEntity();
returnViewable.setUserName(username);
returnViewable.setIsMobile(isMobile);
}
}
Tested resource class for completeness
#Path("person")
public class PersonController {
#GET
#UserInModel
#Produces(MediaType.TEXT_HTML)
public PersonView person() {
Person person = new Person("peeskillet#fake.com");
return new PersonView(person);
}
}
I have a Spring MVC controller that handles requests to do with user management including a method to save user details and a method to allow a user to reset their password. I want to use a validator to ensure that the user typed the same password twice.
My controller
#Controller
public class UserDetails {
...
#InitBinder
public void binder(WebDataBinder binder) {
binder.addValidators(new PasswordValidator());
}
...
#RequestMapping(value="/saveUserDetails", method=RequestMethod.POST)
public String saveUserDetails(
#ModelAttribute User user) {
...
}
...
#RequestMapping(value="/resetPassword", method=RequestMethod.POST)
public String resetPassword(
#Validated PasswordPair password, BindingResult result) {
...
}
And the Validator
private final static class PasswordValidator implements Validator {
private final static int MIN_LEN=5;
#Override
public boolean supports(Class<?> clazz) {
return PasswordPair.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
PasswordPair pair = (PasswordPair)target;
//1 impose password rules
if (pair.getPassword().length()<MIN_LEN) {
errors.rejectValue("password", "Too short", "Password must bne at least "+MIN_LEN+" chars");
}
if (!pair.getPassword().equals(pair.getConfirmPassword())) {
errors.rejectValue("confirmPassword", "mustMatch", "passwords must match");
}
}
}
The problem is that when saveUserDetails is called spring is trying to validate the user with the password validator.
I have tried changing #InitBinder to #InitBinder("password") or #InitBinder("PasswordPair") but in that case nothing is validated at all.
How can I make it validate only the correct parameters?
Your approach of using #InitBinder(withname) should work , my guess is that you have just provided the wrong name.
Can you try this name:
#InitBinder("passwordPair")
Another option will to explicitly give your ModelAttribute a name, this way and use that name in the InitBinder:
public String resetPassword(
#ModelAttribute("password") #Valid PasswordPair password, BindingResult result) {
...
}
#InitBinder("password")
On a related note, I have a similar issue recorded with Spring Jira and have a pull request to change this behavior in place. Please vote it up if possible - https://jira.springsource.org/browse/SPR-11429
Add password and confirmPassword field to the User object instead of having PasswordPair object. Change resetPassword method to have user as inparameter.
And change the validator to validate the user. Here i suppose that you would have password validation even when create a new user?
Ex:
#Controller
public class UserDetails {
#InitBinder
public void binder(WebDataBinder binder) {
binder.addValidators(new PasswordValidator());
}
#RequestMapping(value="/saveUserDetails", method=RequestMethod.POST)
public String saveUserDetails(#ModelAttribute User user) {
...
}
#RequestMapping(value="/resetPassword", method=RequestMethod.POST)
public String resetPassword(#Valid #ModelAttribute User user, BindingResult result) {
...
}
And the Validator
private final static class UserValidator implements Validator {
private final static int MIN_LEN=5;
#Override
public boolean supports(Class<?> clazz) {
return PasswordPair.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
User user = (User)target;
//1 impose password rules
if (user.getPassword().length()<MIN_LEN) {
errors.rejectValue("password", "Too short", "Password must bne at least "+MIN_LEN+" chars");
}
if (!user.getPassword().equals(user.getConfirmPassword())) {
errors.rejectValue("confirmPassword", "mustMatch", "passwords must match");
}
}
}
or you could look at this post, maybe it'll help you:
Handling password confirmations on spring-mvc
I have been looking for a way to somehow reduce the amount of code that is duplicated with subtle variance in my Spring MVC controllers, but searching through the SO questions so far has only yielded some questions without any satisfactory answers.
One example of duplication that I want to remove is this, where the user creation page and the role creation page share similarities:
#RequestMapping(value = "user/create", method = RequestMethod.GET)
public String create(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Displaying user creation page.");
return "user/create";
}
#RequestMapping(value = "role/create", method = RequestMethod.GET)
public String create(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Displaying role creation page.");
return "role/create";
}
A slightly more involved variant of duplication that I would like to remove is the one for posting the create form:
#RequestMapping(value = "user/create", method = RequestMethod.POST)
public String save(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Entering save ({})", user);
validator.validate(user, errors);
validator.validatePassword(user, errors);
validator.validateUsernameAvailable(user, errors);
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
if (errors.hasErrors()) {
return create(user, errors);
} else {
service.save(user);
}
return "redirect:/user/index/1";
}
#RequestMapping(value = "role/create", method = RequestMethod.POST)
public String save(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Entering save({})", role);
validator.validate(role, errors);
if (errors.hasErrors()) {
return create(role, errors);
} else {
service.save(role);
}
return "redirect:/index";
}
This example includes a validate then save if correct and a redirect to the error page if things don't go as planned.
How to remove this duplication?
Spring uses your handler method parameter types to create class instances from the request parameters or body. As such, there is no way to create a handler (#RequestMapping) method that could take an Object and check if it is either a Role or a User. (Technically you could have both parameters and just check which one isn't null, but that is terrible design).
Consequently, you need a handler method for each. This makes sense since, even through the logic is similar, it is still specific to the exact type of model object you are trying to create. You perform different validation, call a different service method, and return a different view name.
I say your code is fine.
Thought I would provide the solution that I settled on in the hope that it might help someone. My gf suggested that I use the name of the entity as a path variable for the controller, and this has proved to provide a very nice solution for the problem at hand.
The two methods now look like this:
#RequestMapping(value = "{entityName}/create", method = RequestMethod.GET)
public String create(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Displaying create page for entity named: [{}]", entityName);
return handlerFactory.getHandler(entityName).getCreateView();
}
#RequestMapping(value = "{entityName}/create", method = RequestMethod.POST)
public String save(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Saving entity of type {}", entityName);
CrudHandler handler = handlerFactory.getHandler(entityName);
handler.getCreateValidator().validate(entity, errors);
if (errors.hasErrors()) {
return create(entityName, entity, errors);
}
handler.preSave(entity);
handler.getService().save(entity);
return "redirect:" + DASHBOARD_URL;
}
The CrudHandler interface has implementations for each entity, and provides the controller with the entity specific classes that it needs, such as service and validator. A sample CrudHandler implementation looks like this for me:
#Component
public class RoleCrudHandler implements CrudHandler {
private static final String ENTITY_NAME = "role";
public static final String CREATE_VIEW = "role/create";
public static final String EDIT_VIEW = "role/edit";
#Resource
private RoleService roleService;
#Resource
private RoleValidator validator;
#Resource
private CrudHandlerFactory handlerFactory;
#PostConstruct
public void init() {
handlerFactory.register(ENTITY_NAME, this);
}
#Override
public GenericService getService() {
return roleService;
}
#Override
public Validator getCreateValidator() {
return validator;
}
#Override
public Validator getUpdateValidator() {
return validator;
}
#Override
public BaseEntity createEntity() {
return new Role();
}
#Override
public void preSave(BaseEntity entity) {
}
#Override
public String getCreateView() {
return CREATE_VIEW;
}
#Override
public String getUpdateView() {
return EDIT_VIEW;
}
}
If someone sees some ways to improve this, feel free to share. Hope this will be of use for someone.