how to do validation on only changed field which come as json on update api.
project use:
java+maven+hibernate+spring mvc
#Entity
#Component("user")
#Table(name="user")
#DynamicUpdate
#Inheritance(strategy = InheritanceType.JOINED)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Range(min = 0, max = 1000000)
#Column(name="user_id")
private int UserId;
#Size(min = 6, max = 20)
#Column(name="password")
//#org.hibernate.annotations.Type(type="StringType")
private String password;
#Size(min=2, max=20)
#Column(name="first_name")
private String firstName;
#Size(min=2, max=20)
#Column(name="last_name")
private String lastName;
#Size(min=2,max=20)
#Column(name="surname")
private String surName;
#Email
#Column(name="email_address")
private String EmailAddress;
#Size(min = 10, max = 10)
#Phone(message = "please enter valid phone number")
#Column(name="mobile_number")
private String mobileNumber;
}
and my update api as bellow:
#SuppressWarnings({ "unchecked", "rawtypes" })
#PostMapping(value = "/candidate")
public ResponseEntity addCandidate(#Valid #RequestBody Candidate candidate, BindingResult result) {
Map<String, Object> errors = new HashMap<String, Object>();
if (result.hasErrors()) {
here save error in errors list.
}
errors.put("error", errorList);
return new ResponseEntity(errors, HttpStatus.NOT_ACCEPTABLE);
}
candidateService.addCandidate(candidate);
return new ResponseEntity(HttpStatus.CREATED);
}
due to the above logic, it will check all attributes which do not update actually.
what are other methods to apply validation only on changed attributes?
more explanation:
I have three class
user
candidate
employee
here user class contains all common information of candidate and employee and in a forther password also contains for login.
now when first time signs up, I want to do validation only on email + password only, but here due to #valid annotation, it does validation on all the attribute.
Related
I'm using Springboot and I want to create a profile.html page where the User can visualise and edit its informations.
Now, there are 2 tables on my db :
Credentials (login phase)
User (provides user informations
Credentials class :
#Entity
public class Credentials {
public static final String DEFAULT_ROLE = "DEFAULT";
public static final String ADMIN_ROLE = "ADMIN";
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
#NotEmpty
// #Size(min = 5, max = 250)
private String username;
#Column(nullable = false)
#NotEmpty
// #Size(min = 8, max = 20)
private String password;
#Column(nullable = false)
private String role = "DEFAULT";
#OneToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private User user;
public Credentials() {
}
public Credentials(String email, String newPassword,long newId) {
this.username=email;
this.password=newPassword;
this.id=newId;
this.user= new User();
}
//some getters and setters
}
And User class :
#Getter
#Setter
#Entity
#Table(name = "users")
public class User {
public User() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String name ="";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
In profile page I want to show all User informations (in this case just its name) so I was thinking to get the current user inside ProfileController, add it to Model class and show all informations needed using html.
The problem is that I don't know how to get the current user.
I think the best way to do this is through Id inside Credentials class because it is the foreign key of User table but I don't know how to get this id. Maybe Springboot Authentication class can help?
I was thinking to retrieve, don't know how, current Credentials class with Authentication and use its id to get the linked User (fetchType.EAGER).
This is pseudocode for ProfileController page :
#Controller
public class ProfileController {
#Autowired
UserService us;
#RequestMapping(value="/profile",method = RequestMethod.GET)
public String showProfilePage(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentUserName = authentication.getName();
Credentials c = getCredentialsFromService() //How I can do this?Is it the right way?
User currentUser = c.getUser();
model.addAttribute("user", currentUser ) ;
return "profile";
}
}
I have REST api with User model - DTO and Create / update form. My userService checks if user is administrator, then allow to getAllUsers in List. When I want to get all users, I get Bad request 400, but it should return Forbidden. It used to work but when I added some changes to my code I got bad request. I don't know what I'm missing...
My User.java
///Lombok annotations
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(AccessLevel.NONE)
#Column(unique = true)
private Long id;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Include
#Column(nullable = false, unique = true)
private UUID uuid = UUID.randomUUID();
#Column(unique = true, nullable = false, length = 254)
private String login;
#Column(nullable = false, length = 254)
private String firstName;
#Column(nullable = false, length = 254)
private String lastName;
#Enumerated(EnumType.STRING)
private RoleType roleType;
#Column(nullable = false, length = 254)
private String password;
#Email
#Column(nullable = false, length = 254)
private String email;
#Positive
private Double cost;
public User(String login, String firstName, String lastName, RoleType roleType, String password,
String email, Double cost) {
this.login = login;
this.firstName = firstName;
this.lastName = lastName;
this.roleType = roleType;
this.password = password;
this.email = email;
this.cost = cost;
}
UserController
#GetMapping("users")
public ResponseEntity<List<UserDto>> getAllUsers(#RequestParam UUID uuid) {
return userService.getListResponseEntity(uuid);
}
UserService
public ResponseEntity<List<UserDto>> getListResponseEntity(UUID adminUuid) {
if (authService.adminAuth(adminUuid)) {
List<User> users = userRepo.findAll();
List<UserDto> userDto = users
.stream()
.map(user -> userMapper.mapToUserDto(user))
.collect(Collectors.toList());
return new ResponseEntity<>(userDto, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
}
UserDto
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class UserDto {
private String login;
private String firstName;
private String lastName;
private RoleType roleType;
private String email;
private Double cost;
I think you missed uuid parameter in request header.
It will be like this. http://localhost:8080/users?uuid="enter_your_uuid_here"
There could be changes that your JSON request data is not matching with you DTO data fields.
Validate following points in your JSON request
there could be issue of wrong field Name
may be not sending proper data as per the datatype.
I'm using a query in my spring boot project with hard coded values and that is fine:
#Query("select user from Users user where user.mobileNumber=?1 and not user.status=-2")
Users FindNotDeletedUserByMobileNumber(String MobileNumber);
But, I wanted to use not hardcoded values, eg. reading from an enum, I tried this :
#Query("select user from Users user where user.mobileNumber=?1 and not user.status=com.taxikar.enums.User_Status.Deleted")
Users FindNotDeletedUserByMobileNumber(String MobileNumber)
But this one gives error while building :
'userRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract com.taxikar.entity.Users com.taxikar.repository.UserRepository.FindNotDeletedUserByMobileNumber(java.lang.String)!
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588) ~[spring-beans-4.3.14.RELEASE.jar:4.3.14.RELEASE]
I use this enum values in my other classes and they are working fine, for example:
if (user.getStatus() == User_Status.Deleted.getId())
return new BaseResponse(BR_Status.error.getId(), BR_ErrorCodes.NotAuthorizedUser.getStringValue() + "01",
"error 755", user.getId());
Even using .getId or .getStringValue like the one above but at end of my query doesn't solve anything. What am I doing wrong ?
Here is my enums code :
public enum User_Status implements IResponse
{
Deleted(-2),
Unauthorized(-1),
NotCompleteGeneralInfo(0),
CompleteGeneralInfo(1);
private int value;
private String stringValue;
User_Status(int value)
{
this.value = value;
}
User_Status(String stringValue){this.stringValue=stringValue;}
#Override
public int getId()
{
return value;
}
#Override
public String getStringValue()
{
return stringValue;
}
}
This enum implements IResponse which is like this :
public interface IResponse
{
String getStringValue();
int getId();
}
Here Is my repository :
public interface UserRepository extends JpaRepository<Users, String>
{
#Query("select user from Users user where user.mobileNumber=?1 and not user.status=com.taxikar.enums.User_Status.Deleted")
Users FindNotDeletedUserByMobileNumber(String MobileNumber);
}
And here is my entity class :
#Entity
#Table(name = "users")
public class Users
{
// these fields are feed by us not the user
#Id
#GeneratedValue(generator = "uuid2")
#Column(columnDefinition = "char(36)")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id;
#Column(name = "STATUS") // User status ===>-2: Deleted , -1: unauthorized , 0: user info is not complete , 1: complete user
private int status;
#Column(name = "RATE")
private String rate;
//Not Optional fields
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "SEX") // Sex ====> 1:women 2:men
private int sex;
#Column(name = "MOBILE_NUMBER")
private String mobileNumber;
#Column(name = "USER_IMG")
private String userImg;
#Column(name = "IDENTITY_NUMBER")
private String identityNumber;
#Column(name = "USER_IDENTITY_CARD_IMG")
private String userIdentityCardImg;
//Optional fields
#Column(name = "EMAIL")
private String email;
#Column(name = "BIRTHDAY")
private String birthday;
#Column(name = "DESCRIPTION")
private String description;
// not Optional fields for driver
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DRIVER_DETAIL")
private DriverDetail driverDetail;
//Login related fields
#Column(name = "TOKEN")
private String token;
#Column(name = "TOKEN_TIMESTAMP")
private Timestamp tokenTimeStamp;
#Column(name="SMS_COUNT")
private int smsCount;
#Column(name="SMS_COUNT_TIMESTAMP")
private Timestamp smsCountTimeStamp;
+++ constructor and setters and getters.
}
Try this:
#Query("select user from Users user where user.mobileNumber=?1 and user.status<>?2")
Users FindNotDeletedUserByMobileNumber(String MobileNumber, int status);
and pass in -2 as parameter when you call that repository method
I'm having a weird issue on my Spring backend, where I am sending a post request with a domain User object from my angular2 frontend that is being recieved by a REST API endpoint and translating it into the Spring User model object. The issue is that the password field from the JSON request seems to not be translating to the password field on the backend. All the other fields (username, name, email, etc.) are coming through fine, but the password is null.
Request payload as seen from network tab in chrome
email : "asdf#asdf.com" firstName : "asdfasdjk" lastName : "asdfs"
login : "adfsjk" password : "fasdfasdfasdsd"
Response seen from network tab
error: "Internal Server Error" exception: "javax.validation.ConstraintViolationException" message: "Validation failed for classes [domain.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=password, rootBeanClass=class domain.User, messageTemplate='{javax.validation.constraints.NotNull.message}'}]"
path:"/api/users" status: 500
Spring Rest method:
#RequestMapping(value = "/users",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<User> createUser(#RequestBody User user) throws URISyntaxException {
User result = userRepository.save(user);
return ResponseEntity.created(new URI("/api/users/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert("user", result.getId().toString()))
.body(result);
}
Spring domain object
#Entity
#Table(name = "user")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User extends AbstractAuditingEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Size(max = 100)
#Column(length = 100, unique = true)
private String login;
#JsonIgnore
#NotNull
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
#Size(max = 50)
#Column(name = "first_name", length = 50)
private String firstName;
#Size(max = 50)
#Column(name = "last_name", length = 50)
private String lastName;
#Email
#Size(max = 100)
#Column(length = 100, unique = true)
private String email;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_tag",
joinColumns = #JoinColumn(name = "users_id", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "tags_id", referencedColumnName = "ID"))
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<Tag> tags = new ArrayList<>();
}
Angular2 domain object
export class User {
id: number;
login: string;
password: string;
firstName: string;
lastName: string;
email: string;
}
The problem is with using #JsonIgnore on the password field, this makes the filed ignorable when reading from or writing to JSON message, one solution is to use #JsonProperty with access = Access.WRITE_ONLY as follows
public class User extends AbstractAuditingEntity implements Serializable {
// Other fileds
#NotNull
#JsonProperty(access = Access.WRITE_ONLY)
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
}
Another solution is to implement a getter and setter for the password field, and annotate the getter with #JsonIgnore and the setter with #JsonProperty, as follows
public class User extends AbstractAuditingEntity implements Serializable {
// Other fileds
#NotNull
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
#JsonIgnore
public String getPassword() {
return password;
}
#JsonProperty
public void setPassword(String password) {
this.password = password;
}
}
I have a controller method that handles the POST request for a reset password form. The form has a hidden input field for the reset token and prompts the user for a new password.
I'd like to use the #Valid annotation in my code below, but not sure if I can use my existing User class.
Does Hibernate Validator require me to create an additional class specifically for the form?
Controller method
#RequestMapping(value = "/resetPassword", method = RequestMethod.POST)
public ModelAndView setNewPassword(#RequestParam Map<String,String> requestParams) {
User user = userService.findUserByResetToken(requestParams.get("token"));
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", "Form data is " + requestParams.get("token") + requestParams.get("password") + user.getEmail());
modelAndView.setViewName("resetPassword");
return modelAndView;
}
User class
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "email")
#Email(message = "Please provide a valid e-mail")
#NotEmpty(message = "Please provide an e-mail")
private String email;
#Column(name = "password")
#Size(min = 8, max = 72, message = "Your password must be between 8 and 72 characters long")
#NotEmpty(message = "Please provide a password")
#Transient
private String password;
#Column(name = "first_name")
#NotEmpty(message = "Please provide your first name")
private String firstName;
#Column(name = "last_name")
#NotEmpty(message = "Please provide your last name")
private String lastName;
#Column(name = "enabled")
private boolean enabled;
#Column(name = "confirmation_token")
private String confirmationToken;
#Column(name = "created_on")
private Date createdOn;
#Column(name = "last_login")
private Date lastLogin;
#Column(name = "reset_token")
private String resetToken;
// Getters and setters omitted
}
You ought to create a separate class, also better to use that class directly as your request body to be able to apply #Valid on it;
Controller Method
#RequestMapping(value = "/resetPassword", method = RequestMethod.POST)
public ModelAndView setNewPassword(#RequestBody #Valid PasswordUpdateRq passwordUpdateRq) {
User user = userService.findUserByResetToken(passwordUpdateRq.getToken());
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", "Form data is " + passwordUpdateRq.getToken() + passwordUpdateRq.getPassword() + user.getEmail());
modelAndView.setViewName("resetPassword");
return modelAndView;
}
New Request Bean
public class PasswordUpdateRq {
#Size(min = 8, max = 72, message = "Your password must be between 8 and 72 characters long")
#NotEmpty(message = "Please provide a password")
private String password;
private String token;
// Getters and setters omitted
}
This will make validation automatic as you've requested. The reason User class is not usable here is that there are other fields with #NotEmpty etc restrictions, which does not exist in your current form, and these would always fail the validation.
Extra comment: I think using the same object for controller layer validation & operations with database creates a high coupling between two, I'd recommend keeping table related classes under logic classes, and use interface beans that are endpoint specific in controller layer.