Spring boot - Combine DTO and form validation - java

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.

Related

Spring Boot. Forward on RestController

I am implementing a mechanism for replacing short links.
I need to forwarded request to another controller. I found examples how to do it in spring on models, but I don't understand how to do it in RestControllers
Example what i found (use models)
#Controller
public class ShrotLinkForwardController {
#RequestMapping("/s/*")
public String myMethod(HttpServletRequest request) {
return "forward:/difmethod";
}
}
Or maybe I'm looking in the wrong direction and I need to make a filter?
UPD. I don't know the final endpoint, it is calculated in the forwarded method. So, i cant autowired other controller
There are 2 ways to achieve what you want.
1. Call the method on the target controller directly.
Controllers are just normal Spring beans. You can get it via autowire.
#Controller
public class ShrotLinkForwardController {
#Autowired
OtherController otherController;
#RequestMapping("/s/*")
public String myMethod(Model model) {
otherController.doStuff();
return ...;
}
}
2. Trigger the forward by returning a string
To trigger the forward, try returning a String instead of ModelAndView.
This is the approach you mentioned in your question. Note that the syntax should be forward:/forwardURL. The string after forward: is the URL pointing to another controller, not the method name.
#Controller
public class ShrotLinkForwardController {
#RequestMapping("/s/*")
public String myMethod(Model model) {
return "forward:/forwardURL";
}
}
you could inject the target controller and simply call the method
#Controller
public class ShortLinkForwardController {
#Autowired
private RestController target;
#RequestMapping("/s/*")
public String myMethod(HttpServletRequest request) {
return target.myMethod(request);
}
}
Caveat: Path related request properties will still point to "/s/*"
Or use ResponseEntity and set target location...
public ResponseEntity<Void> myMethod(HttpServletRequest request) {
return ResponseEntity.status(302).location(URI.create(...)).build();
}
All answers are about returning String
But I've found another solution
Maybe it will help someone with my problem in case when you need to make forward from one REST endpoint to another REST endpoint.
And it also could be applied to your case.
#RestController
public class CustomerController {
#GetMapping("/forwarding_endpoint")
public void makeForward(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().getServletContext().getRequestDispatcher("/forward_endpoint").forward(request, response);
}
}
UPD. I don't know the final endpoint, it is calculated in the
forwarded method. So, i cant autowired other controller
but I don't understand how to do it in RestControllers
I can see some indications of possible bad design here, so I will try to explain the possible issues and how should be handled according to best practices.
If your requirement is to make a forward to another controller, then this might be an indication of 3 possible issues:
The job to be done by the other controller (which you say you want to forward to) can be extracted into a service method in service layer. Then both controllers can call the same service method, without each controller be aware of the other.
Your need could also be an indicator of the following issue. You need 2 controllers for exactly the same practical reason, so that they provide for same input exactly the same output, but to be available from 2 different URLs. If this is the case then you can use just 1 controller and allow it to be executed for both URLs. See the following code to achieve this:
#RequestMapping({"/s/*", "/s2/*})
public String myMethod(HttpServletRequest request) {
return "some response";
}
You need to expose only 1 URL to the client which will serve everything. Then the approach with forward will also not benefit you, since the client will be able to reach the other forwarded controller directly if he wishes so. In this case you can implement 1 single controller which then according to the needs builds different responses. You can do this in RestController although not suggested by Sonar and other code review tools by marking the method to return ResponseEntity<?>. Example:
#RequestMapping("/s/*")
public ResponseEntity<?> myMethod(HttpServletRequest request) {
if (condition 1) {
return new ResponseEntity<YourObject1>(HttpStatus.OK);
} else if (condition 2) {
return new ResponseEntity<YourObject2>(HttpStatus.OK);
} else {
return new ResponseEntity<YourObject3>(HttpStatus.OK);
}
}
this last choice is not considered best practice with <?> but for this requirement I don't see any other way out.

Bypassing uniqueness validator for PUT request

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.

Same controller returning different amounts of information depending on role in Spring Boot Rest Controller

In my REST server, I have a controller with the mapping "/users":
#RequestMapping(value = "/users", method = RequestMethod.GET)
public List<UserModel> getAllUsers() {...}
I would like to use Spring Boot Security with Roles to determine how much information to return. For example:
public class UserModel {
private MetaDataModel user_metadata;
private MetaDataModel private_admin_metadata;
}
So that when getAllUsers is called by a user with the admin role, the UserModel supplies both fields, otherwise it only supplies the user_metadata field. I'm completely open to the idea that there is a better approach to this so any suggestions are welcome!
Set appropriate property of response DTO. And use include not null json annotation. Refer For Spring Boot 1.2.3, how to set ignore null value in JSON serialization?

Spring Rest Web Request Scope

I'm in the process of writing a Spring REST type interface to a database that will retrieve user specific results for various resources.
To hold the user I have a spring #Component annotated bean called CurrentUser as a temporary measure.
#Component
public class CurrentUser {
#Autowired
private UserDAO userDAO;
private String userId;
private String email;
private String notes;
public String getUserId() {
return userId;
}
public void setUserId(String userId) throws ApiException {
User user = userDAO.getUser(userId) // Database call to
if (!user.isValid()) {
throw ApiException(...) // The exception would crash back to the user as a error in the response
}
// Valud user so set these aspects.
this.userId = user.userId;
this.email = user.email;
}
}
This object is initialised in a Spring Interceptor on each call to any method in the API using the following interceptor.
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
#Autowired
private CurrentUser user;
#Autowired
private RequestParameters requestParameters;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ApiException {
user.setUserId(StringUtils.defaultString(request.getParameter("userId"), "defaultUser"));
return true;
}
}
This is only a place-holder to identify the users until proper authentication can be added.
I'm relatively new to Spring, and the reason for this post
is to increase my understanding of the thread safety aspects of Spring in situations like this
I've recently discovered that Spring is not automatically thread safe, I may need to give more consideration to the scopes.
What I want to understand is the following:
For the above setup, is there any danger that 1000s of simultaneous requests, would potentially interfere and overwrite each other?
e.g. A request for one user would potentially be overwritten with a different user from a separate http request, causing the requestor to receive the wrong data.
What would be the best approach to solving this. (Even though it will be replaced, I have other objects instantiated in similar ways)
Options I'm looking at (if this is an issue), is setting prototype scope, or attaching to the request / session directly rather than allowing them their own autowired object.
Any information anyone could give me would be much appreciated, as I'm a fan of getting it right(er) to start with, than dealing with bad assumptions later on.
Answer 1: Yes, and you don't need 1000 requests to get into trouble. 2 requests in parallel are enough.
Answer 2:
The main problem here is one of scoping:
Default scope of Spring managed beans is Singleton. That means that only one instance of your CurrentUser exists per Application.
That is obviously not what you want. (Since you have a severe security issue here, with only one CurrentUser instance per application).
Simple Answer:
I would probably use Spring Security ;-)
Even Simpler Answer:
If that is not an option:
Use a Filter instead of a HandlerInterceptor (more direct control over clean up)
Create a Thread Local to store the user (and use a finally in the Filter to clean it up) and set it in the Filter
Create a request scoped Service (use #ScopedProxy, to be able to wire it into Singletons), that accesses the ThreadLocal as a UserService (you will need an interface to make it work easily)
Autowire this UserService where you need it
Since by specification each request in a Servlet environment is bound to a thread, and thread locals are inherently thread-safe, you are completely thread safe and it will scale well. The overhead of the scoped proxy is minimal.
(This is only one option, other options could make use of the request scope explicitly or use aspects in a slightly more elegant manner. However, it is a rather simple approach and gets the job done. For more complex needs I would seriously recommend looking into Spring Security).
You ca use parameter resolver feature of spring mvc without making it a bean.
to do that implement the interface HandlerMethodArgumentResolver and register that into the container. And then your handler method can have a argument of type current user
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver{
#Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.getParameterType().equals(CurrentUser.class)) {
return true;
}
return false;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
if (supportsParameter(parameter)) {
String userId = (String) webRequest.getAttribute("userId", NativeWebRequest.SCOPE_REQUEST);
return new CurrentUser(userId);
}
return null;
}
public class CurrentUser{
public CurrentUser(String userId) {
// TODO Auto-generated constructor stub
}
}
}
After this you can have handler method of stype
#RequestMapping
public String handler(CurrentUser user){
....
}

Spring MVC3 #SessionAttributes and #ModelAttribute will automatically get value from request

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;
}
}

Categories