I would like to ask your ideas about designing best validation approach for below requirements:
We have a User model class and depending on it is status we can update some specific fields. As you can see, changeability of the model class depends on its status field.
If the status of user is ACTIVE then all fields (name, surname,
password ....) can be updated.
If the status of user is INACTIVE only password can be updated.
If the status of user is BLOCKED then name and surname can be
updated.
If the status of user is DELETED then update operation is not
allowed for any field.
Obviously, it can be done simply by adding a UserValidator class and before setting values in setter methods I can call my UserValidator to check if the operation is allowed or not. However, it has a drawback (?): what will happen if there will be new field (let's say maritalStatus) and dev who adds that field forgets to call UserValidator before setting maritalStatus?
Other ways of solving this problem that I can think of:
Using custom annotations by extending CustomValidator. However, it
won't work as annotation cannot know the previous values of object.
I mean, the isValid() method of CustomValidator won't know if the name
field has changed or not (it was "John" and now dev wants to change it
to "Jack")
Proxy pattern could be useful but not sure if it is good idea to use
proxy for model objects.
I've seen that the decorator pattern can be used for this problem but I don't understand how. I think validating model class is beyond the responsibility of a decorator design.
public class User {
private Integer id;
private String name;
private String surname;
private String password;
private Status status;
// setters
}
public enum Status {
ACTIVE, DELETED, INACTIVE, BLOCKED
}
I suggest you to use intention-revealing interfaces instead of setter/getter
E.g.
public class User {
private Integer id;
private String name;
private String surname;
private String password;
private Status status;
void rename(String newName, String newSurname){
if(Status.INACTIVE.equals(status) || Status.DELETED.equals(status))
throws new Exception("You cannot rename a inactive or deleted user");
this.name = newName;
this.surname = newSurname;
}
.....
}
Here is the approach I will use. Would be very nice if you guys can also vote for that.
Basicly I will use Decorator pattern.
public interface User {
Integer getId();
void setId(Integer id);
String getName();
void setName(String name);
String getSurname();
void setSurname(String surname);
String getPassword();
void setPassword(String password);
Status getStatus();
void setStatus(Status status);
}
public class UserImpl implements User {
private Integer id;
private String name;
private String surname;
private String password;
private Status status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
public abstract class UserDecorator implements User{
private final User user;
public UserDecorator(User user) {
this.user = user;
}
public Integer getId() {
return user.getId();
}
public void setId(Integer id) {
user.setId(id);
}
public String getName() {
return user.getName();
}
public void setName(String name) {
user.setName(name);
}
public String getSurname() {
return user.getSurname();
}
public void setSurname(String surname) {
user.setSurname(surname);
}
public String getPassword() {
return user.getPassword();
}
public void setPassword(String password) {
user.setPassword(password);
}
public Status getStatus() {
return user.getStatus();
}
public void setStatus(Status status) {
user.setStatus(status);
}
}
InActive User:
public class InactiveUserImpl extends UserDecorator {
public InactiveUserImpl(User user) {
super(user);
}
// didn't override setPassword therefore that field can be updated when the status of user is Inactive
#Override
public void setId(Integer id) {
throw new IllegalArgumentException("Field can not be update when user is InActive");
}
#Override
public void setName(String name) {
throw new IllegalArgumentException("Field can not be update when user is InActive");
}
#Override
public void setSurname(String surname) {
throw new IllegalArgumentException("Field can not be update when user is InActive");
}
#Override
public void setStatus(Status status) {
throw new IllegalArgumentException("Field can not be update when user is InActive");
}
}
Active User:
public class ActiveUserImpl extends UserDecorator {
public ActiveUserImpl(User user) {
super(user);
}
// not overriding any method hence everything can be updated
}
Basically, whoever will ask for UserImpl I will return him wrapped version of UserImpl, For example,
class UserRepository{
public User getById(String id){
User user=db.getUserById(id);
// this can be done with Enum but it is out of our scope
if (user.getStatus().equals(INACTIVE))
return new InactiveUserImpl(user);
// ....
return null;
}
}
If you do :
User user= userRepository.getById(1)
then you will get user with the sets of allowed setters.
Related
I am trying to build a table that can be modified and stored in a sql database. I am using mybatis to access my sql database. I want to be able to take a String from the user and add a column to the existing table that I have. In order to add a column to the sql table, I need to modify the class that fetches the data, and this class needs to have an instance variable added and the constructor modified to be able to fetch the data.
For example, This is one of my Mapper classes that relies on the Employee class to fetch data from the database.
#Mapper
public interface EmployeeMapper {
#Select("SELECT * FROM employee")
List<Employee> findEmployees();
#Select("SELECT * FROM employee WHERE list_UserName = #{list};")
List<Employee> employeeForUser(String list);
#Insert("INSERT INTO employee(name, email, position, list_UserName) values (#{name}, #{email}, #{position}, #{list_UserName});")
void addEmployee(Employee employee);
#Delete("DELETE FROM employee WHERE id = #{id}")
void deleteEmployee(int id);
}
Employee Class:
public class Employee {
private int id;
private String name;
private String email;
private String position;
private String list_UserName;
public Employee(int id, String name, String email, String position, String list_UserName) {
this.id = id;
this.name =name;
this.email = email;
this.position = position;
this.list_UserName = list_UserName;
}
public String getList_UserName() {
return list_UserName;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPosition() {
return position;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setEmail(String email) {
this.email = email;
}
public void setPosition(String position) {
this.position = position;
}
}
Here is my controller for this case:
#RequestMapping(value="/list", method = RequestMethod.POST)
public String newEmployee(Model model, #RequestParam String EmployeeName, #RequestParam String EmployeeEmail, #RequestParam String EmployeePosition, HttpSession session) {
if(!sessionFound(session.getId())){
return "redirect:/login";
}
if(isNewEmployee(returnUser(session.getId()).getUser_name(), EmployeeName, EmployeeEmail, EmployeePosition)) {
Employee temp = new Employee(0, EmployeeName, EmployeeEmail, EmployeePosition, returnUser(session.getId()).getUser_name());
employeeMapper.addEmployee(temp);
return "redirect:/list";
}
model.addAttribute("employeeMessage", "Employee Already Exists");
return "redirect:/list";
}
In this case, I want to be able to alter the table(which I know how to do) and add an instance variable to the Employee class based on the User's request(which I do not know how to do). How do I do that?
I want to design the database in my application using room. I have created all necesary classes. My add query is working fine. But when I use a select query and try to fetch some data from the database, it returns null object. I don't find the problem in it. My entity is User class. I have created User dao, User repository, and User viewmodel. In my fragment I want to fetch data from the database. Part of my fragment is:
private boolean invalidPass(String user, String pass) {
boolean invalid = false;
LiveData<User> userLog = nUserViewModel.getUserByPhone(user);
if (!userLog.getValue().getPass().equals(pass))
invalid = true;
return invalid;
}
My dao is here:
#Dao
public interface UserDao {
#Insert
public void addUser(User user); //this works fine
#Query("SELECT * from user_info") //does not work
List<User> getAllUsers();
#Query("SELECT * from user_info where phone= :phoneN") //does not work
public LiveData<User> getUserByPhone(String phoneN);
#Query("UPDATE user_info SET pass = :passN where phone= :phoneN")
public void updatePassword(String passN, String phoneN);
#Delete
public void deleteUser(User user);
}
Repository:
public LiveData<User> getUserByPhone(String phone) {
userNow = mUserDao.getUserByPhone(phone);
return userNow;
}
view model:
public LiveData<User> getUserByPhone(String phone) {
return mRepository.getUserByPhone(phone);
}
Error shows:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String <package name>.User.getPass()' on a null object reference
The user object is:
public User(#NonNull String phone, String name, String dob, String address, String email, String pass) {
this.phone=phone;
this.name = name;
this.dob=dob;
this.address=address;
this.email=email;
this.pass=pass;
}
#NonNull
public String getPhone() {
return phone;
}
public void setPhone(#NonNull String uPhone) {
this.phone = uPhone;
}
public String getName() {
return name;
}
public void setName(String uName) {
this.name = uName;
}
public String getDob() {
return dob;
}
public void setDob(String uDOB) {
this.dob = uDOB;
}
public String getAddress() {
return address;
}
public void setAddress(String uAddress) {
this.address = uAddress;
}
public String getEmail() {
return email;
}
public void setEmail(String uEmail) {
this.email = uEmail;
}
public String getPass() {
return pass;
}
public void setPass(String uPass) {
this.pass = uPass;
}
You need to Observe the LiveData with a Observer and in the Observer you are getting the User Object. It is async so your method would not work because you don't have direct access to the User object and that is why you are getting a NullPointerException. Simply because the User Object isn't directly available.
If you are in a Fragment you call addObserver() on the LiveData passing getViewLifecycleOwner() and a Observer.
I am trying to update a field on my Object and then trying to save it. The code is like this in the controller that will be called.
ApplicationUser user = applicationUserRepository.findByVerificationCode(verificationCode);
if(user != null) {
user.setVerified(true);//trying to change a value in a field
applicationUserRepository.save(user);
return new ResponseEntity<>(user,new HttpHeaders(),HttpStatus.OK);
}
When I try to execute this code, I get this error
E11000 duplicate key error index: myapp.applicationUser.$id dup key: { : 0 };
I am defining Id explicitly in the ApplicationUser class.
My ApplicationUser class is like this
public class ApplicationUser {
#Id
private long id;
private String username;
private String password;
private String name;
private String email;
private String verificationCode;
private boolean verified=false;
private List<Company> boughtCompanies;
public long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getVerificationCode() {
return verificationCode;
}
public void setVerificationCode(String verificationCode) {
this.verificationCode = verificationCode;
}
public List<Company> getBoughtCompanies() {
return boughtCompanies;
}
public void setBoughtCompanies(List<Company> boughtCompanies) {
this.boughtCompanies = boughtCompanies;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
What am I doing wrong here or how should I procced? Thanks.
you try to insert an existant user with the same id
function insert i think you should change it with funcion of update
You didnt use the setter of your ID so you need to put it as auto increment
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String password;
private String name;
private String email;
private String verificationCode;
private boolean verified=false;
private List<Company> boughtCompanies;
public long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getVerificationCode() {
return verificationCode;
}
public void setVerificationCode(String verificationCode) {
this.verificationCode = verificationCode;
}
public List<Company> getBoughtCompanies() {
return boughtCompanies;
}
public void setBoughtCompanies(List<Company> boughtCompanies) {
this.boughtCompanies = boughtCompanies;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
Edit:
Set the #Id field as String. It is not good to have long values as ID in mongo.
Also as M. Wajdi said you need to add the setter to the id field.
I see that you are performing an update of a document so you should use applicationUserRepository.save() instead of applicationUserRepository.insert(). (Actually, I always use save).
Explanation:
Insert always try to introduce a new object in the database.
The first time you create the ApplicationUser, insert creates the object in the DB and assign it an ID.
But if you read it, update it and try to insert it again, Mongo will understand that you are actually trying to introduce a new object with the same ID (instead of performing an update in that object).
That's why you get that exception, duplicated key.
While choosing long (or any other primitive) as mongo #Id, then you have to cater the id generation, else everytime it will give long default value i.e. 0
For autoincrement sequencing refer, already answered here Auto increment sequence in mongodb using java.
Else make #Id of type String, mongo autogenerates Default Id of hexadeximal type, for more info refer https://docs.mongodb.com/manual/reference/method/ObjectId/
I am trying to retrieve data from mongodb via spring framework.
At first I made return type Map<String, Object>, but I decided to change to User value object.
Below is the class for User VO
#Document(collection = "user")
public class User {
#Id
#Field(value="id")
private String id;
#Field(value="name")
private String name;
#Field(value="password")
private String password;
#Field(value="professional")
private String professional;
#Field(value="email")
private String email;
#Field(value="gravatar")
private String gravatar;
#PersistenceConstructor
public User(String id, String name, String password, String professional, String email, String gravatar) {
super();
this.id = id;
this.name = name;
this.password = password;
this.professional = professional;
this.email = email;
this.gravatar = gravatar;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getProfessional() {
return professional;
}
public void setProfessional(String professional) {
this.professional = professional;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGravatar() {
return gravatar;
}
public void setGravatar(String gravatar) {
this.gravatar = gravatar;
}
};
and Here is #repository to retrieve data
#Repository
public class MongoMemberDao implements CommonDao<String, Map<String, Object>, Exception> {
#Autowired
MongoTemplate template;
final String COLLECTION_NAME = "user";
#SuppressWarnings("unchecked")
#Override
public Map<String, Object> read(String key) throws Exception {
Query findQuery = new Query();
findQuery.addCriteria(Criteria.where("id").is(key));
return template.findOne(findQuery, Map.class, COLLECTION_NAME);
}
public User readByDocument(String id) throws Exception {
Query findOneQuery = new Query();
findOneQuery.addCriteria(Criteria.where("id").is(id));
return template.findOne(findOneQuery, User.class, COLLECTION_NAME);
}
};
read method returns fine, but readByDocument does not(returns null not User instance). I read official document. But I do not get any clue of it.
FYI, The parameter Query looks same for both.
Query: { "id" : "system"}, Fields: null, Sort: null
I want to know why readByDocument returns null
Thanks.
---- Edit
Follow is my Database Config
#Configuration
public class MongoConfig extends AbstractMongoConfiguration {
private final String MONGO_URL = "127.0.0.1";
private final Integer MONGO_PORT = 27017;
#Override
protected String getDatabaseName() {
return "tfarm";
}
#Override
// #Bean
public Mongo mongo() throws Exception {
return new MongoClient(MONGO_URL, MONGO_PORT);
}
}
And I added this to WebApplictaionInitializer implement.
For current solution
I found follow on official site
A field annotated with #Id (org.springframework.data.annotation.Id)
will be mapped to the _id field.
A field without an annotation but named id will be mapped to the _id
field.
The default field name for identifiers is _id and can be customized
via the #Field annotation.
So I changed my VO like...
#Document(collection = "user")
public class User {
#Id
private ObjectId _id;
#Field(value="id")
private String id;
#Field(value="name")
private String name;
#Field(value="password")
private String password;
#Field(value="professional")
private String professional;
#Field(value="email")
private String email;
#Field(value="gravatar")
private String gravatar;
#PersistenceConstructor
public User(String id, String name, String password, String professional, String email, String gravatar) {
super();
this.id = id;
this.name = name;
this.password = password;
this.professional = professional;
this.email = email;
this.gravatar = gravatar;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public ObjectId get_id() {
return _id;
}
public void set_id(ObjectId _id) {
this._id = _id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getProfessional() {
return professional;
}
public void setProfessional(String professional) {
this.professional = professional;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGravatar() {
return gravatar;
}
public void setGravatar(String gravatar) {
this.gravatar = gravatar;
}
};
Added ObjectId. In alternative, just removing #Id annotation works fine too. However
#Id
#Field(value="id")
String id;
will not work. Thanks for help.
I want to see is there a better way to achieve this: currently, I have a User class, UserRequest class and UserResponse class, they are all very similar or subset of User.
class User{
long id;
String name;
String password;
String field1;
String[] array1;
}
class UserRequest{
String name;
String password;
}
class UserResponse{
long id;
String name;
String field1;
}
So instead of having 3 similar classes, can I limit the fields using User class for the ResponseEntity? or what would be a better way to achieve what i am trying to do without having all the similar classes?
public #ResponseBody ResponseEntity<UserResponse> login(#RequestBody #Valid UserRequest request) {
User user = userRep.findByPrimaryEmailLike(request.getPrimaryEmail());
return new ResponseEntity<UserResponse>(user.getSuccessLoginResponse(), HttpHeaderUtils.getHeader4Json(), HttpStatus.OK);
}
Depends on what your format and (de-)serialization handler is, but making the wild guess it's JSON via Jackson:
import org.codehaus.jackson.annotate.JsonIgnore;
class User {
long id;
String name;
String password;
String field1;
String[] array1;
#JsonIgnore
public String[] getArray1() {
return array1;
}
#JsonIgnore
public void setArray1(String[] array1) {
this.array1 = array1;
}
public String getField1() {
return field1;
}
#JsonIgnore
public void setField1(String field1) {
this.field1 = field1;
}
public long getId() {
return id;
}
#JsonIgnore
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
If you use a SpringMVC extension like Yoga, you can return only the User object, but customize the request to determine which fields are returned in the #ResponseBody. For example,
GET http://mydomain.com/user?selector=name,password
GET http://mydomain.com/user?selector=id,name,field1