I am faced with a problem of not being able to get around a single validator that is used for checking the uniqueness of the name of an object upon POST and PUT requests. Details are provided below:
Given,
A class UserDTO with two fields
private int id
#UserUniquenessValidator
private String name
The controller methods signatures
post(#Valid #RequestBody UserDTO userDTO)
put(#PathVariable int id, #Valid #RequestBody UserDTO userDTO)
A custom validator applied on the field name
#UserUniquenessValidator
Now, whenever I am trying to POST a new user the custom validator simply checks the name field against other records in the database and returns true if it does not find any and vice-versa.
The problem comes about every time a PUT request, with the field 'name' not changed, is sent in - the validator checks for uniqueness and does not let through as it already has an entry with the given name in the DB.
On a side note, I should mention that the constraint cannot be applied on the table in the database for reasons that are too long to explain.
Is there a clever walk-around solution to this without having to clutter the code too much? I wish there was a way of letting the validator know that anytime a PUT comes in to follow a different logic than for a POST request.
I can assume your validator is already a Spring bean: you retrieve database records to pursue name uniqueness.
Provided that the validator is involved only in the request validation context, you can inject a HttpServletRequest proxy to give the validator a request context
#Autowired
private HttpServletRequest request;
from which you can figure out the current HTTP method.
#Override
public boolean isValid(String name, ConstraintValidatorContext context) {
return HttpMethod.PUT.name().equals(request.getMethod()) ||
validate(name, context);
}
private boolean validate(String name, ConstraintValidatorContext context) {
// validate name uniqueness
return false;
}
To avoid hardcoding HttpMethod.PUT.name(), the annotation can define String[] excludeFor:
#UserUniquenessValidator(excludeFor={"PUT"})
I find the annotation name incorrect. UserUniquenessValidator is a good title for a class that processes this annotation but not for the annotation itself. I would name it #UniqueName.
Related
I have an EJB application that consists of two beans, ServiceEJB (web tier) and BusinessEJB (business tier), where BusinessEJBis injected in ServiceEJB.
ServiceEJBreceives HTTP requests from the browser, calls a method in BusinessEJB, gets the result, and sends the HTTP response.
Also, ServiceEJB has access to the HttpSession object, where the userId of the user that logged in is stored. BusinessEJBdoes NOT have access to the HttpSession object.
The application needs to log messages (using sl4j/logback, for example). It could log the message in ServiceEJBor BusinessEJB methods, and when it logs a message, it has to include the userId of the session in the log entry.
Since BusinessEJB doesn't have the userId, it needs to get it from ServiceEJB. The question is what is the best way to achieve that. What I DON'T want to do is to add a userId field to each method in BusinessEJB as a parameter, as there are many ServiceEJBs and BusinessEJBs in the application (and other beans called by BusinessEJB that also generate log entries), and I don't want to pollute the application with the userId field. Instead, I could have a userId field at the EJB level, but how to populate them? Is there a way to achieve this with annotations? Any suggestions will be welcome.
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
#Stateless
public class ServiceEJB {
#Context
HttpServletRequest httpRequest;
#Inject
private BusinessEJB bean;
private String userId;
#Path("someurl")
public Response someMethod1() {
final HttpSession session = httpRequest.getSession();
// get the userId from the session
String s = bean.someMethod2();
// return Response
}
}
#Stateless
public class BusinessEJB {
private String userId;
public String someMethod2() {
// .... log an entry with userId
return "something";
}
}
A few pointers/comments:
If you integrate with application server security, then the user name is available at any component. EJBs can get it by calling getCallerPrincipal() on the injected variant of the EJBContext, here the javax.ejb.SessionContext:
#Resource
private SessionContext sessionCtx;
Servlets can retrieve the principal from the HttpServletRequest.getUserPrincipal(). JAX-RS components (the ServiceEJB) can retrieve it from the javax.ws.rs.core.SecurityContext.getUserPrincipal().
Is there any reason why you are NOT integrating with the application server security?
If you have a good reason NOT to integrate with application server security, I would propose a variation of the solution from the previous answer. The variation is to set the user data from a filter applied to all resources (either servlet filter or JAX-RS ContainerRequestFilter), so that you do not have to worry about setting it in multiple places.
If you ONLY NEED THE USER ID FOR LOGGING, I'd suggest you take a look at the concept of Mapped Diagnostic Contexts (MDC) in slf4j. With it you can set the user id early at the beginning of the request and make it available to all logging statements thereafter.
Create a request scoped CDI bean i.e. UserContext.
Inject it into both EJBs.
In ServiceEJB set user's id and in BusinessEJB read it.
I am currently developing an API where I'm using DTO for the first time. So far I've used Spring's form validation with javax.validation.
So my question is if there is a way to combine both DTO and "form" validation. Let me explain myself: lets say I have a service to log in and another to register. In the service to register we have: name, password and email, the 3 of them must be filled. As for the login service, only the email and password must be filled. So we'd have something like:
private String name;
private String password;
private String email;
Until now, what I did was to create a POJO per request (forms) and then use annotations such as #NotNull but now with DTO in the project I'm in now they just have the same DTO and business object with the same properties and no constraints.
How could I do what I was usually doing? Checking the fields that must be not null in the controller looks a little dirty to me and I can't just put something like #NotNull in the UserDTO because then in the two examples I said I'd have to send also the name when logging in although it's not needed for that service.
So, how could I combine these 2 things? Is this something not possible or there's a better approach?
Thanks.
I assume you are using two separate controllers for login and register requests.
And if it is the case, then you can make good use of org.springframework.validation.Validator interface:
#Component("registrationValidator")
public class RegistrationValidatorImpl implements Validator {
#Override
public boolean supports(final Class<?> aClass) {
}
#Override
public void validate(final Object o, final Errors errors) {
}
}
Create RegistrationValidatorImpl and LoginValidatorIml and #Autowire it in your controllers.
The usage of validator is simple:
invokeValidator(registrationValidator, someDTO, errors);
if (errors.hasErrors()) {
return new ResponseEntity(HttpStatus.BAD_REQUEST); //or whatever logic here
}
The controller method signature should be similar to this:
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity register(#RequestBody final SomeDTO someDTO, final HttpServletRequest request, final Errors errors) {}
I case of one controller, I assume you have different methods mapped to login and register requests. You can #Autowire both validators in controller and use each in separate methods.
Using groups for validation with javax.validation did the work. I followed the answer in this question (as Andrew suggested), then I just had to put every field I wanted to have different rules in different groups.
I’m trying to develop a Spring MVC application, now I encounter a question. When login successful I add the User entity to session and call http://localhost:8080/user to get the session user. Everything is OK here. But if I call the URL like this http://localhost:8080/user?username=testuser then the session user's username will change to testuser. What should I do that just get current user from session?
The code likes below
Entity:
#Entity
public class User {
private Long id;
private String username;
// ...Getter and Setter...
}
Controller:
#Controller
#RequestMapping("user")
#SessionAttributes("current_user")
public class UserController {
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public User testSession(#ModelAttribute("current_user") User user) {
return user;
}
}
Response of http://localhost:8080/user
[{"id":1,"username":"aaa111"}]
Response of http://localhost:8080/user?username=testuser; it should be same as above, but is
[{"id":1,"username":"testuser"}]
The #SessionAttributes annotation isn't intended for this. Its intend is to store objects in the session during http requests. Imagine a lengthy database call to retrieve an object you don't want to retrieve this object each time but probably reuse an existing one. The object is to be intended to be used as a #ModelAttribute, this annotation indicates that you want to use this object for binding (i.e. you have a form to change attributes of the object). When you are finished with the editing of the object you should make this clear by calling setComplete() on the SessionStatus object. See also here.
You want to store an object in the session and retrieve it when you need it. For this use the HttpSession in the normal way of calling setAttribute and getAttribute. To obtain the current HttpSession you can simply add a method argument of the type HttpSession and it will be injected for you. (See here for a list of supported method arguments).
public void myRequestHandlingMethod(HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
}
Or as you are already using Spring you could use the WebUtils for convenience. You can use the getSessionAttribute or getRequiredSessionAttribute methods to obtain the value from the session.
public void myRequestHandlingMethod(HttpServletRequest request) {
User currentUser = (User) WebUtils.getSessionAttribute("currentUser", request)
}
Another solution would be to extend Spring MVC. Spring MVC uses a HandlerMethodArgumentResolver to handle all the different types of method arguments. This mechanism is pluggable. You could create an annotation #CurrentUser and create a CurrentUserHandlerMethodArgumentResolver that will retrieve the user from the session and injects it in that place. You could then simply add your current user to your method signature.
public void myRequestHandlingMethod(#CurrentUser User user) { ... }
Configure the custom argument resolver
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.yourcomponany.app.web.CurrentUserHandlerMethodArgumentResolver />
</mvc:argument-resolvers>
</mvc:annotation-driven>
It also looks like you are rolling your own security framework, which I would advice against. Instead I would suggest using Spring Security instead. Advantage of this is that this provides integration with the Servlet API allowing for retrieval of the current Principal by either doing it yourself (request.getUserPrincipal()) or simply adding a method argument of the type java.security.Principal. It also comes with a custom HandlerMethodArgumentResolver which allows you to obtain the current Spring Security Authentication object.
try to get session value in controller from servlet request like below
#Controller
#RequestMapping("user")
#SessionAttributes("current_user")
public class UserController{
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public User testSession(HttpServletRequest request){
//false means do not create new session
HttpSession session = request.getSession(false);
return session != null?session.getAttribute("current_user"):null;
}
}
I want to validate my MyAccount form using a custom Spring Validator.
Basic validation rules are no problem.
I now have this requirement, which seems pretty obvious and common:
If the user (currently logged in) changes his username, I'll have to check if it's already in use. If it's not been changed, I'm fine (it would definitely be in use when checked, that's why I don't want to check it in that case). The problem is, that the validator is a Spring managed Singleton and I don't seem to have access to the current session (where I store my login context - i.e. not Spring Security). But I need the currently logged in user, in order to compare its e-mail with the one entered in the form.
This is my scenario, but the question is actually about how to validate using an object from the user's session, in general.
The ways I was thinking about solving this:
Do only basic validation in the Validator and do the rest, that I need the session for, in the Controller. Doesn't seem to be a nice solution.
#RequestMapping(value="/myaccount", method=RequestMethod.GET)
public String myAccount(#Valid MyAccountForm form, BindingResult result, HttpSession session)
{
boolean hasUsernameChanged = // check using session
if (hasUsernameChanged && CustomerService.customerAlreadyExists(form.getUsername()))
result.rejectValue("username", "my.error.code");
if (result.hasErrors()) {
// handle errors
} else {
// proceed
}
}
Adding a second validate method to the Validator like so
public void validateWithCurrentCustomer(Customer current) {
...
}
and call it explicitly from the controller, with the appropriate object. Not much better, but at least the validation logic is in one class, although separated in two methods, one of which is not standard.
Not having the Validator a Spring managed (singleton) bean, but create it everytime in the initBinder method. Instead of:
#Autowired
public MyAccountController(MyAccountFormValidator validator)
{
this.validator = validator;
}
#InitBinder
protected void initBinder (WebDataBinder binder)
{
binder.setValidator(validator);
}
do
#InitBinder
protected void initBinder (WebDataBinder binder, HttpSession session)
{
Customer current = ...// get from session
binder.setValidator(new MyAccountFormValidator(current));
}
But here, the problem is, that since the validator is not Spring managed, it's hard to get a service (like CustomerService) injected into the validator, for checking if an e-mail is available or not.
Am I missing something, any other ways to achieve what I want? This seems to be a common problem, but I couldn't find any pattern on Google or SO.
Thanks for your hints.
You may try to access any Spring bean from your validator. Maybe this answer can help.
Where should I place validation code that requires access to database?
Basically, you can make a SpringBeanUtil singleton, which gives you access to any bean you want.
I want to register a custom HandlerMethodArgumentResolver that could handle the following #Controller handler method definition
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
I can register my resolver, which just creates a Domain object through request parameters, by overriding addArgumentResolver() from WebMvcConfigurerAdapter. When Spring tries to resolve the Domain parameter, it goes through its list of HandlerMethodArgumentResolver (there are a lot) and picks the first one that supports() it.
In the above example, although my resolver will get called and my Domain argument will get initialized, the #Valid annotation won't have been processed and the resolver for BindingResult, an ErrorsMethodArgumentResolver will fail because it requires a #ModelAttribute, #RequestBody or the #RequestPart argument in the handler method, which I don't have.
If I try to fix it by adding #ModelAttribute
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid #ModelAttribute Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
a HandlerMethodArgumentResolver implementation, ModelAttributeMethodProcessor, will get checked first with supports() and resolve the argument (with #ModelAttribute and #Valid) before my custom resolver. The BindingResult won't fail, but I won't have my custom creation behavior on the Domain instance.
I could just copy-paste the code for validation and adding to model that's in ModelAttributeMethodProcessor, but I was hoping there was an easier way to resolve my parameters and perform validation without adding an object to the model. Is there such a way?
Nice description of the issue that you are facing.
I checked out the code that you have outlined and have come to the same conclusion that you have - there is no built-in way to have both a custom HandlerMethodArgumentResolver as well as #Valid related validation applied at the same time, the only choice is to do what the ModelAttributeMethodProcessor does which is to check if the parameter has a #Valid annotation and call the validation logic related code.
You can probably derive your HandlerMethodResolverArgumentResolver from ModelAttributeMethodProcessor and call super.validateIfApplicable(..) atleast this way the existing code is leveraged.
It's may be too late, but your HandlerMethodArgumentResolver gets WebDataBinderFactory object as last argument, then, to hook up the validation, simply add this to your resolver implementation:
Object resolvedObject = // your logic
if(parameter.hasParameterAnnotation(Valid.class){
binderFactory.createBinder(webRequest,resolvedObject,"resolvedObjectLogicalName").validate ();
}