Wicket Form changes randomly being ignored - java

I had a problem with a Form Field (Transient wicket form field ignored) and I had no much luck with the answers, so I made some changes and now the field is sometimes updated, and sometimes not... here's the current code:
The User entity:
#Entity(name = "user")
#Audited
public class User extends DataObjectAudit {
private static final long serialVersionUID = 1L;
private RoleTypeEnum role = null;
public RoleTypeEnum getRole() {
return role;
}
public void setRole(RoleTypeEnum role) {
this.role = role;
}
}
And in the panel, there is a drop down panel (extends GenericPanel) and the options possible are the current active role of the user is loaded among with the "lower" possible roles. The chosen option should be loaded into the "role" property.
User sessionUser = StudySession.getSessionUser();
List<RoleTypeEnum> roles = new ArrayList<RoleTypeEnum>();
roles.addAll(sessionUser.getRole().getLowerAndEqualsThanSelf());
WefDropDownPanel<RoleTypeEnum> role = new WefDropDownPanel<RoleTypeEnum>(helper.of(RoleTypeEnum.class, "role").errorRequired(), roles).setSizes(Size.S0, Size.S1, Size.S4);
add(role);
There is no validation for that field, others have though.
The only thing that is made before saving the form is:
#Override
protected void onBeforeSave(AjaxRequestTarget target, WefForm<User> form) {
User user = getModelObject();
DataService dataService = ServiceFactory.getBean(DataService.class);
ProjectCenter projectCenter = dataService.findUniqueByParameters(ProjectCenter.class, new Parameter<Object>("center", user.getCenter()));
if (projectCenter != null) {
user.setProject(projectCenter.getProject());
}
}
So the result is that sometimes "role" is updated, and sometimes it is not... Also, all the other fields (that I have erased to simplify) are also being ignored...
If I erase the onBeforeSubmit method, the result is the same... sometimes the entity is updated with the values the user entered, sometimes not...
The funny thing is that sometimes the entity is updated, and sometimes it is not... most of the time it works...

Create a quickstart and report back on the user list - I'll happily debug the problem.

Related

Update/notify other User in Spring Web

I have some design/implementation issue that I just can't wrap my head around it. I am currently working on a text-based game with multiple players. I kind of understand how it works for Player-to-Server, I meant that Server sees every individual Player as the same.
I'm using spring-boot 2, spring-web, thymeleaf, hibernate.
I implemented a custom UserDetails that returns after the user login.
#Entity
#Table(name = "USER")
public class User implements Serializable {
#Id
private long userId;
#Column(unique = true, nullable = false)
private String userName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "playerStatsId")
private PlayerStats stats;
}
public class CurrentUserDetailsService implements UserDetailsService {
#Override
public CurrentUser loadUserByUsername(String userName) {
User user = this.accountRepository.findByUserName(userName)
.orElseThrow(() ->
new UsernameNotFoundException("User details not found with the provided username: " + userName));
return new CurrentUser(user);
}
}
public class CurrentUser implements UserDetails {
private static final long serialVersionUID = 1L;
private User user = new User();
public CurrentUser(User user) {
this.user = user;
}
public PlayerStats getPlayerStats() {
return this.user.getStats();
}
// removed the rest for brevity
}
Hence, in my controller, I can do this to get the CurrentUser.
*Note each User is also a player.
#GetMapping("/attackpage")
public String viewAttackPage(#AuthenticationPrincipal CurrentUser currentUser) {
// return the page view for list of attacks
return "someview";
}
The currentUser here would reflect to the current user per say (Player 1 or 2 or 3 and so on). Which works fine for most of the stuff happening to themselves such as purchasing some stuff, updating profile and so on.
But what I can't get or know how to achieve is when 2 players interact.
For example, Player 1 attacks Player 2. If I am Player 1, what I'll do is to click the "Attack" on the View and select the Player 2, and submit the command. Hence, in the controller, it will be something like this.
#GetMapping("/attack")
public String launchAttack(#AuthenticationPrincipal CurrentUser currentUser, #RequestParam("playername") String player2) {
updatePlayerState(player2);
return "someview";
}
public void updatePlayerState(String player) {
User user = getUserByPlayername(player);
// perform some update to player state (say health, etc)
// update back to db?
}
Here's is what really got me confused.
As seen previously, when each User/Player logs in, a set of user (player) current state will be pulled from the DB and store "in-memory".
Hence, when Player 1 attacks Player 2,
How do I "notify" or update Player 2 that the stats has changed, and thus, Player 2 should pull updated stats from db to memory.
How to tackle the possible concurrency issue here? For example, Player 2 health is 50 in DB. Player 2 then perform some action (say purchase health potion + 30), which then update the DB (health to 80). However, just before the DB is updated, Player 1 has already launch the attack and grab from DB the state of Player 2 where it will return 50 since DB has yet to be updated. So now, whatever changes made in getUserByPlayername() and update to the DB will be wrong, and the entire state of the Player will be "de-sync". I hope I am making sense here.
I understand that there is #Version in hibernate for optimistic locking but I'm not sure if it's applicable in this case. And would spring-session be useful in such case?
Should I not store the any data in memory when user login? Should I always be retrieving data from DB only when some action is performed? Like when viewProfile, then I pull from accountRepository. or when viewStats then I pull from statsRepository and on so.
Do point me in the right direction. Would appreciate for any concrete example of sort, or some kind of video/articles. If there is any additional information required, do let me know and I'll try to explain my case better.
Thank you.
I think that you should not be updating the currentUser in your Controller methods, and should not be relying on the data in that object to represent a player's current state. There are probably ways to get that to work, but you'd need to mess around with updating the security context.
I also recommend that you lookup Users by id instead of userName, so will write the rest of this answer with that approach. If you insist on finding Users by userName, adjust where necessary.
So, keeping it simple, I would have a reference to the accountRepository in the Controller, and then, whenever you need to get or update a player's state, use
User user = accountRepository.findById(currentUser.getId())
Yes, #Version and optimistic locking will help with the concurrency issues that you're concerned about. You can reload the Entity from the database, and retry the operation if you catch an #OptimisticLockException. Or, you may want to respond to player 1 with something like "Player 2 has just purchased a potion of healing, and is now 80 heath, do you still want to attack?"
I'm not a spring user, but I think that the problem is more conceptual than technical.
I'll try to provide an answer which uses a general approach, while writing the examples in a JavaEE style so that they should be understandable, and hopefully, portable to spring.
First of all: every single DETACHED entity is stale data. And stale data is not "trustable".
So:
each method that modify the state of an object should re-fetch the object from DB inside the transaction:
updatePlayerState() should be a transaction-boundary method (or called inside a tx), and getUserByPlayername(player) should fetch the target object from the DB.
JPA speaking: em.merge() is forbidden (without proper locking, i.e. #Version).
if you (or spring) are doing this already, there's little to add.
WRT the "lost update problem" you mention in your 2. be aware that this covers the application server side (JPA/Hibernate), but the very same problem could be present on DB side, which should be properly configured for, at least, repeatable read isolation. Take a look at MySQL does not conform to Repeatable Read really, if you are using it.
you have to handle controller fields that refer stale Players/Users/Objects. You have, at least, two options.
re-fetch for each request: suppose Player1 has attacked Player2 and diminished Player2 HP by 30. When Player2 goes to a view that shows his HP, the controller behind that view should have re-fetched the Player2/User2 entity before rendering the view.
In other words, all of your presentation (detached) entities should be, sort of, request-scoped.
i.e you can use a #WebListener to reload your Player/User:
#WebListener
public class CurrentUserListener implements ServletRequestListener {
#Override
public void requestInitialized(ServletRequestEvent sre) {
CurrentUser currentUser = getCurrentUser();
currentUser.reload();
}
#Override
public void requestDestroyed(ServletRequestEvent sre) {
// nothing to do
}
public CurrentUser getCurrentUser() {
// return the CurrentUser
}
}
or a request-scoped bean (or whatever-spring-equivalent):
#RequestScoped
public class RefresherBean {
#Inject
private CurrentUser currentUser;
#PostConstruct
public void init()
{
currentUser.reload();
}
}
notify other controller instances: if the update succeeded a notification should be sent to other controllers.
i.e. using CDI #Observe (if you have CDI available):
public class CurrentUser implements UserDetails {
private static final long serialVersionUID = 1L;
private User user = new User();
public CurrentUser(User user) {
this.user = user;
}
public PlayerStats getPlayerStats() {
return this.user.getStats();
}
public void onUpdate(#Observes(during = TransactionPhase.AFTER_SUCCESS) User user) {
if(this.user.getId() == user.getId()) {
this.user = user;
}
}
// removed the rest for brevity
}
Note that CurrentUser should be a server-managed object.

foreign keys in objectify / google app engine

I'm trying to write an application for the google app engine using Objectify and am having some trouble. I'm new to noSQL datastores so it's probably a conceptual problem.
I have an entity called a message, and each message has a from User - the user who created the message.
#Entity
public class Message {
#Index private Key<User> fromUserKey;
#IgnoreSave private User fromUser;
Annoyingly, I have to have both a User and a Key field in the message. JSON needs the User object to populate the response with useful fields, and the google app engine needs the Key to be able to store the reference to the user. The #IgnoreSave annotation is used to stop Objectify asking the google app engine to try to store the User object (which will fail).
When fetching messages, the from user key is populated, but the from User object is not. Here's what the DAO code looks like for my "getMessages" operation:
public static List<Message> getSentMessages(long userId) {
List<Message> result;
result= ofy().load().type(Message.class).filter("from", Key.create(User.class, userId)).limit(1000).list();
return result;
}
But the fromUser object is not populated on each Message, only the fromUserKey. How do I get the actual User object populated in the response? I need to access such fields as forename, surname, etc - not just the ID of the User. I could get the from User from the DB, loop through the results, then call setFromUSer on each message, but it's ugly. And it also causes a ConcurrentModificationException...
I'm sure I'm doing something wrong here, but I can't work out what.
What you can do is have a property Ref<User> on the Message entity and annotate it with '#Parent'. This means that each Message entity will become part of the user's entity group.
The Ref<?> works like a key but allows you to directly access the actual entity object; that way you can easily get the forename, surname etc.
Change your class as follows:
#Entity
#Cache
public class Message
{
#Id Long id;
#Load #Parent private Ref<User> user;
public User getUser() { return this.user.get(); }
public void setUser(User value) { this.user = Ref.Create(value); }
}
Having done that, you will be able to perform ancestor queries to retrieve all the message entities associated with a particular user:
public static List<Message> getSentMessages(long userId)
{
User parent = ofy().load().type(User.class).id(userId).now();
List<Message> results = ofy().load().type(Message.class).ancestor(parent).limit(1000).list();
return results;
}
You should now be able to do what you wanted, and hopefully not get any more errors.

update method returns null value for complex objects in play framework 2

Play framework 2 update() method acts weirdly in my project.
My project has following model classes:
#entity
public class Images{
#Id
public Long id;
#Lob
public byte[] imageFile;
}
#entity
public class Users {
#Id
public Long id;
public String name;
public Blobs photo;
}
This is index.scala.html:
#(user : Users)
<p>#user.id</p>
<p>#user.name</p>
<p>#user.photo.id</p>
edit user no. 1
And this is the code in my editUser.scala.html:
#(id : Long, userForm : Form[Users])
#form(action = routes.Application.update(id))
#inputText(userForm("name"))
#inputText(userForm("photo.id"))
<input type="submit" value="UPDATE">
Here's my controller class:
public class Application extends Controller {
public static Result index() {
return ok(index.render(Users.find.byId(1L)));
}
public static Result editUser(Long id) {
Form<Users> userForm = form(Users.class).fill(Users.find.byId(id));
return ok(
views.html.editUser.render(id, userForm)
);
}
public static Result update(Long id) {
Form<Users> userForm = form(Users.class).bindFromRequest();
userForm.get().update(id);
return redirect((routes.Application.index()));
}
}
Assume that there is one entry in Blobs class with id=1 and a blob and there is one entry in Users class also with id=1. Now I want to update that user and set the photo id from the Blobs table.
The problem is when I try to update user from editUser form and refer back to index.scala.html, I get a null pointer exception. It seems update() method just returns null for photo field in Users form, though it has no problem updating name value of the user. I checked with userForm.field("photo").value() and it seems that bindFromRequest works properly. But after updating, the photo field is just null. Does anyone know what the problem could be?
Edit:
P.S: This program will begin with a null pointer exception, because photo field of Users is null at the beginning of the program. Just assume we go directly to the edit page and we try to update Users with a photo from Blobs. The problem is even after the update, I get null pointer exception and it seems update doesn't affect that field in User class

How to save an ArrayList in Play?

This might seem like a very basic question, but I have a model (User) which I want to store an ArrayList of Strings (they are the id's of other users). I declare the List like this:
public List<String> friends = new ArrayList<String>();
After I add an entry to the array, I save the user. But friends is always null when I try to use it. Is there a specific way to save an ArrayList? Any help would be appreciated.
My model:
#Entity
public class User extends Model {
#Id
public String username;
public String password;
public List<String> friends = new ArrayList<String>();
public static Finder<String, User> find = new Finder<String, User>(String.class, User.class);
// Constructor
public User(String username, String password){
this.username = username;
this.password = password;
}
// Methods
public void addFriend(String friend){
friends.add(friend);
}
// Static Methods
public static User authenticate(String username, String password){
return find.where().eq("username", username).eq("password", password).findUnique();
}
public static void befriend(String user1, String user2){
User.find.ref(user1).addFriend(user2));
User.find.ref(user2).addFriend(user1);
User.find.ref(user1).save();
User.find.ref(user2).save();
}
}
The controller method:
return ok(index.render(
User.find.byId(request().username()).friends,
));
And a very simple view:
#(friends: List[User])
<div id="current_friends">
#for(friend <- friends) {
#friend.username
}
</div>
You need to save the relations 'manually' with saveManyToManyAssociations(String fieldname), for an example:
public static void befriend(String userName1, String userName2){
User user1 = User.find.byId(userName1);
User user2 = User.find.byId(userName2);
user1.friends.add(user2);
user2.friends.add(user1);
user1.save();
user2.save();
// here...
user1.saveManyToManyAssociations("friends");
user2.saveManyToManyAssociations("friends");
}
(note: written from top of my had so debug it yourself pls)
One potential reason for this problem could be your view:
The first line of your view is
#(friends: List[User])
The User does not have a package name, which could cause the null pointer exception.
In my case, my User bean is under models package, so I have the following line:
#(friends: List[models.User])
I encountered the exact same problem, and here is how I fixed it (with a little explanation coming along).
In fact, you try to save an ArrayList (thus something which size is undefined) in a DataBase. And apparently (and quite logically), the Play Framework doesn't really like it ; you have to use whether annotations or a transient class. I decided to use the class way (also because i don't know how to use the annotations to make a sub table, so I didn't took the risk, but it's not the best way to do it. In fact, it's an horrible way of doing it. But still, here it is).
In your case, you could to this :
#Entity
public class Friends extends Model {
#Id
public Long id;
#Required
public String user1;
#Required
public String user2;
public static Finder<Long, Friends> find = new Finder<Long, Friends>(Long.class, Friends.class);
//Here put your functions, I myself only added an insert method for the moment :
public static void add(String user1, String user2){
Friends f = new Friends();
f.user1 = user1;
f.user2 = user2;
bu.save();
}
}
And in your User model, just change the part in which you save both user into each other's List by this function.
Hope this will help.
Note : the id is here because I like numeric ids, feel free to change it.
Note 2 : Of course, it would be much better to use #ManyToOne and #OneToMany annotations, but as I wrote before, I don't know exactly how does it work.

JPA 2 - OneToOne cross reference

I have been struggling with this issue for a while now, and can't seem to find a solution anywhere. Maybe I have misunderstood something, but would really appreciate it if someone could clear up things for me.
I am trying to create a OneToOne bidirectional relationsship between a User.class and a FacebookProfile.class.
That is I want to be able to look up the corresponding User entity from the FacebookProfile entity, and vice versa:
userInstance.getFbprofile();
fbprofileInstance.getUser();
My User.class:
#Entity
#Access(value=AccessType.FIELD)
public class User implements UserDetails {
#OneToOne(mappedBy="user")
private FacebookProfile fbprofile;
}
My FacebookProfile.class:
#Entity
#Access(value=AccessType.FIELD)
public class FacebookProfile {
#OneToOne (cascade=CascadeType.ALL)
#JoinColumn(name="USER")
private User user;
}
The new FacebookProfile and User instances are created in a Controller, and then sent through a Service layer to a DAO class which persists the objects.
I first create and persist a new User object.
I then create a new FacebookProfile and puts the newly created User in it (setUser). I then persist the FacebookProfile object.
The User ID is now stored in the FacebookProfile database table, but there is no reference from the User to the FacebookProfile so the following code returns a NullPointerException:
User tempUser = userService.findUserById(newUser.getId());
System.out.println("ID "+tempUser.getFbprofile().getId());
Below is the code from the Controller if something is unclear:
//Check if the Facebook user already exist
FacebookProfile fbprofile = facebookProfileService.findFacebookProfileById(fbId);
User newUser;
//Create a new FacebookProfile if it doesn't exist
if(fbprofile == null){
//Check if there is a user registered with the facebook email
newUser = userService.findUserByEmail(fbEmail);
// No User and No FacebookProfile exists
if(!newUser.isEnabled()){
newUser = new User();
newUser.setFirstname(fbFirstname);
newUser.setLastname(fbLastname);
//Set email
Email mail = new Email();
mail.setAddress(fbEmail);
mail.setName("Facebook");
newUser.addEmail(mail);
//Set gender
if(fbGender.equalsIgnoreCase("female")){
newUser.setGender(Gender.FEMALE);
}else if(fbGender.equalsIgnoreCase("male")) {
newUser.setGender(Gender.MALE);
}
userService.createUser(newUser);
}
FacebookProfile newProfile = new FacebookProfile();
newProfile.setId(Long.parseLong(fbId));
newProfile.setUsername(fbUsername);
newProfile.setFirstname(fbFirstname);
newProfile.setLastname(fbLastname);
newProfile.setName(fbName);
newProfile.setEmail(fbEmail);
if(!fbHometown.equals("")){
newProfile.setHometown(fbHometown);
newProfile.setHometownID(Long.parseLong(fbHometownID));
}
if(!fbLocation.equals("")){
newProfile.setLocation(fbLocation);
newProfile.setLocationID(Long.parseLong(fbLocationID));
}
newProfile.setLink(fbLink);
if(fbGender.equalsIgnoreCase("male")){
newProfile.setGender(Gender.MALE);
}else if (fbGender.equalsIgnoreCase("female")) {
newProfile.setGender(Gender.FEMALE);
}
newProfile.setUser(newUser);
this.facebookProfileService.createNewFacebookProfile(newProfile);
//newUser.setFbprofile(newProfile);
}else { //There already exists a FacebookProfile
newUser = fbprofile.getUser();
}
I feel like I have tried every possible solution to this and haven't got it to work.
I suspect that in one of my earlier attempts that the fbprofile reference was set in the User object, but never persisted because it was set after the User object was persisted. I am using JPA2 and EclipseLink.
If someone's got a solution to this it would be much appreciated.
Bi-Directional relations is not maintained by JPA. JPA designers had thought that maintaining bi-directional relations can cause deadlocks, inconsistencies... Toplink was supporting bi-directional relations before JPA.. I have used Toplink for many years, I did come accross some inconsistencies related to bi-directional relations.. You should maintain bi-directional relations manually.. Such as ;
newProfile.setUser(newUser);
newUser.setFbProfile(newProfile);
EDIT :
Well, you want me to elaborate my answer.. You should change your code that persist FbProfile entity such as ;
newProfile.setUser(newUser);
newUser.setFbprofile(newProfile);
this.facebookProfileService.createNewFacebookProfile(newProfile);
As an alternative , you could also change FbProfile setUser method ;
public void setUser(User aUser) {
if (this.user==aUser)
return;
if (this.user != null) {
this.user.setFbProfile(null);
}
this.user = aUser;
if (this.user!=null) {
this.user.setFbprofile(this);
}
}

Categories