Enforce unique field in appengine datastore (java) - java

I have a User class with a String field emailAddress, on which I want to enforce uniqueness. I was having difficulty getting the available suggested solutions to work, so I wrote the following process which seems to work, using a separate class with the unique field as its primary key.
Whilst it seems to work, it smells because I'm intentionally creating an exception when nothing incorrect happens (by trying to retrieve a non-existent EmailAddress object).
Can anyone comment on whether this method will work reliably, in particular across multiple appengine instances.
Thanks.
Firstly, I have the User class:
#PersistenceCapable
public class User {
public User(String emailAddress, String otherData) {
this.emailAddress = emailAddress;
this.otherData = otherData;
}
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private String emailAddress;
#Persistent
private String otherData;
}
I then have an EmailAddress class, using the email address as the primary key:
#PersistenceCapable
public class EmailAddress {
public EmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
#PrimaryKey
#Persistent
private String emailAddress;
}
Usage of these classes is then as follows:
public class UserManager {
public User createUser(String email, String otherData) {
User user = new User(emailAddress, otherData);
EmailAddress emailAddress = new EmailAddress(email);
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
User persistedUser = null;
try {
tx.begin();
EmailAddress testEmailAddress = null;
try {
testEmailAddress = pm.getObjectById(EmailAddress.class, email);
} catch (Exception e) {
//
}
if (null != testEmailAddress) {
throw new RuntimeException("That email address already has an account");
}
pm.makePersistent(emailAddress);
persistedUser = pm.makePersistent(user);
txn.commit();
return persistedUser;
} finally {
if (txn.isActive()) {
txn.rollback();
}
pm.close();
}
}
...
}

Related

Vaadin ThreadLocal for User Management

I'm currently developing a web application in Java which will be accessed by multiple users at the same time and as such need to store userdata in order to tailor the application to their individual requirements (such as what company they are apart of etc).
There are 2 classes that i use to manage this. User, MainSystem detailed below:
User
#Entity
public class User implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String username;
private String password;
private String type;
private String company;
private String DOB;
private String email;
private int PayrollId;
public User(String firstName, String lastName, String username, String password, String type, String company, String DOB, String email) {
this.firstName = firstName;
this. lastName = lastName;
this.username = username;
this.password = password;
this.type = type;
this.company = company;
this.DOB = DOB;
this.email = email;
}
MainSystem:
public class MainSystem {
public UserController userController;
private UserRepository userRepository;
private static ThreadLocal<User> loggedInUser = new ThreadLocal<>();
public DbController dbController;
public MainSystem(){
userController = new UserController(userRepository);
loggedInUser.set(new User());
}
public Boolean Login(String username, String password) {
if(userController.checkUser(username,password)){
User aUser = userController.getUser(username);
setLoggedInUser(userController.getUser(username));
VaadinSession.getCurrent().setAttribute("username",loggedInUser.get().getUsername());
System.out.println("Logged in User: "+loggedInUser.get().getUsername());
return true;
}
else {
return false;
}
}
public static void setLoggedInUser(User user){
loggedInUser.set(user);
}
public static User getLoggedInUser() {
return loggedInUser.get();
}
Ideally what i'd like to do is access the ThreadLocal variable from another class, for instance the ViewProfile.View:
public class EditProfileView extends VerticalLayout implements View {
MainSystem main = new MainSystem();
NavigatorUI aUI = new NavigatorUI();
User aUser = main.getLoggedInUser();
TextField username = new TextField("Username");
TextField Id = new TextField("Id");
TextField email = new TextField("Email");
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
TextField type = new TextField("Type");
PasswordField oldPassword = new PasswordField("Current Password");
PasswordField changePassword1 = new PasswordField("New Password");
PasswordField changePassword2 = new PasswordField("Confirm Password");
private UserController userController;
private UserRepository userRepository;
public EditProfileView() {
setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
userController = new UserController(userRepository);
setStyleName("backgroundImage");
setMargin(true);
setSizeFull();
addComponent(aUI.getHeader());
FormLayout content = new FormLayout(generateInfo());
Panel aPanel = new Panel("Edit User",content);
aPanel.setWidthUndefined();
content.setMargin(true);
aPanel.setStyleName(ValoTheme.PANEL_WELL);
addComponent(aPanel);
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
try {
aUser = main.getLoggedInUser();
System.out.println( aUser.getUsername());
Id.setValue(aUser.getId().toString());
username.setValue(aUser.getUsername());
firstName.setValue(aUser.getFirstName());
lastName.setValue(aUser.getLastName());
type.setValue(aUser.getType());
email.setValue(aUser.getEmail());
aUI.setUserMenu();
aUI.refreshPayroll();}
catch (Exception e){
System.out.println(e.getMessage());
}
}}
However, i'm finding that it is presenting me with a "null" value? I fear i may have missunderstood how ThreadLocal works. but essentially what i'm trying to achieve is to Store an instance relevant variable of the User.Class in MainSystem for other classes to use?
Any help would be appreciated.
My Solution:
My solution to this was to store the User.class in a VaadinSession Attribute like so:
public Boolean Login(String username, String password) {
if(userController.checkUser(username,password)){
User aUser = userController.getUser(username);
VaadinSession.getCurrent().setAttribute("user",aUser);
VaadinSession.getCurrent().setAttribute("username",loggedInUser.get().getUsername());
System.out.println("Logged in User: "+loggedInUser.get().getUsername());
return true;
}
else {
return false;
}
}
Then in other classes if i want to use that attribute i retrieved it like so:
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
try {
aUser = (User)VaadinSession.getCurrent().getAttribute("user");
}
catch (Exception e){
System.out.println(e.getMessage());
}
}
The problem is that there's no guarantee that MainSystem.Login() and EditProfileView.enter() will happen on the same thread. Every user action is processed as a separate HTTP request that the servlet container will run on any available thread.
For this kind of functionality, I would instead recommend storing the user information in the HTTP session. If you don't have any custom servlets or such, you could instead have a an field that contains the user object in your own custom UI class. Vaadin takes care of making UI.getCurrent() always return the right value in all code that is run through Vaadin.
If you instead also are integrating with other servlet functionality, you could store the user in the HttpSession instead. Generic servlet code can find the session through the getSession() method in e.g. servlet requests and response. In code run by Vaadin, you can use VaadinSession().getCurrent().getSession() to get a WrappedSession instance that is based on to the same HttpSession data.
There is also another approach. Instead it could be possible to make #SessionScoped user service, and either #Inject or #Autowire that depending whether you are using CDI or Spring. When doing this way, it will be the bean manager that takes care of binding correct entity with your view.

JPA: "Can't write; duplicate key in table" for a One-to-One relationship

I have these entities that I want to relate bi-directionaly.
Credential:
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "credential")
public class Credential extends MetaInfo implements Serializable {
...
private Email email;
...
#OneToOne(cascade = CascadeType.ALL, optional = false, orphanRemoval = true)
#JoinColumn(name="email", referencedColumnName="email_address")
public Email getEmail() {
return email;
}
public void setEmail(Email email) {
this.email = email;
}
...
}
Email:
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "email")
public class Email extends MetaInfo implements Serializable{
...
private Credential credential;
public Email() {
}
public Email(String emailAddress) {
this.emailAddress = emailAddress;
}
#Id
#Column(name="email_address")
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
#OneToOne(mappedBy = "email", optional=false)
public Credential getCredential() {
return credential;
}
public void setCredential(Credential credential) {
this.credential = credential;
}
}
In a CredentialRepository class I am testing whether the passed-in email
is not assigned to any user except for the user with the username passed-in as the second (optional) parameter:
#Override
public boolean emailIsAssigned(String... args) {
assert(args.length > 0);
if(InputValidators.isValidEmail.test(args[0])){
EntityManager em = entityManagerFactory.createEntityManager();
try {
TypedQuery<Long> count = em.createQuery("SELECT COUNT(e) "
+ "FROM Email e WHERE e.emailAddress "
+ "= :email AND e "
+ "IN (SELECT c.email FROM Credential c WHERE c.username "
+ "!= :username)", Long.TYPE).setParameter("email", args[0])
.setParameter("username", null);
if(InputValidators.stringNotNullNorEmpty.apply(args[1])){
//only if the username has been provided
count.setParameter("username", args[1]);
}
return count.getSingleResult() > 0;
} catch (Exception e) {
System.out.println(e.getMessage());
return false;
} finally {
em.close();
}
}else{
throw new NotAValidEmailException(args[0] + " is not a"
+ " valid email address.");
}
}
Thus above args[0] is the email under test and args[1] is the username under test.
And this is the test that is causing me problems (note that before I already successfully tested inserts, updates and even the emailIsAssigned method but without the c.email part which seems to cause the issue:
#Test
public void emailAlreadyExistsTest(){
assertTrue(credentialRepo.emailIsAssigned("existing_email#yahoo.ca"));
}
And this is the error message that I have:
[EL Warning]: 2017-04-17 17:55:33.606--ServerSession(234430897)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Can't write; duplicate key in table '#sql-3e4_9a'
Error Code: 1022
Call: ALTER TABLE credential ADD CONSTRAINT FK_credential_email FOREIGN KEY (email) REFERENCES email (email_address)
Query: DataModifyQuery(sql="ALTER TABLE credential ADD CONSTRAINT FK_credential_email FOREIGN KEY (email) REFERENCES email (email_address)")
I would appreciate if someone could give me a piece of advice. I could always just change the email into a String and mark it as "unique" in #Column, but I feel that there is no reason for the chosen approach not to work.
I am using MySQL as the DB vendor, and Eclipse-Link JPA implementation. I did try to "hard-change" the name of the FK constraint but to no avail. The DB and all tables have the same collation (utf8_unicode_ci).
Try to delete the primary key for class Email because "extends MetaInfo"

Why doesn't my owned one-to-many relationship get persisted to the GAE datastore?

I have two classes defined and mapped to the GAE datastore - a Person class and a LocationStamp class, which represents a single latitude/longitude combination along with a timestamp. There is an owned one-to-many mapping between Person and LocationStamp, implemented on similar lines as a forum post on one-to-many relationships.
In my DAO for Person, I have the following method:
public LocationStamp addLocationStampForCurrentUser(LocationStamp ls)
{
PersistenceManager pm = getPersistenceManager();
Person p = getProfileForCurrentUser();
p.getLocationStamps().add(ls);
pm.currentTransaction().begin();
try {
pm.makePersistent(p);
pm.currentTransaction().commit();
return ls;
} finally {
if (pm.currentTransaction().isActive()) {
pm.currentTransaction().rollback();
}
pm.close();
}
}
When I try using this method to add an entry to the object's collection of LocationStamp entities, the relationship does not get persisted. A later query for the relevant Person object returns the correct object with email address, but the locationStamps list is empty. Moreover, the Data Viewer for the App Engine server does not show any LocationStamp entities (and there isn't any column shown for locationStamps in the table for Person.
I followed the instructions in the forum post carefully, but I'm not sure if I'm missing something.
Here are my entities:
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class Person
{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private String emailAddress;
#Persistent
private String name;
#Persistent(mappedBy = "person")
#Element(dependent = "true")
#Order(extensions = #Extension(vendorName = "datanucleus", key = "list-ordering", value = "timestamp desc"))
private final List<LocationStamp> locationStamps = new ArrayList<LocationStamp>();
// ... getters and setters ...
}
and
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class LocationStamp
{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private double latitude;
#Persistent
private double longitude;
#Persistent
private Date timestamp;
#Persistent
private boolean automatic;
#Persistent
private Person person;
// ... getters and setters ...
}
Since they're used in the above code, here are the definitions for a couple of other methods:
public Person getProfileForCurrentUser()
{
PersistenceManager pm = getPersistenceManager();
try {
Key k = getKeyForEmailAddress(getCurrentUserEmail());
return pm.getObjectById(Person.class, k);
} catch (JDOObjectNotFoundException e) {
// Create a new profile if the new one isn't found
return updateProfileForCurrentUser(new Person());
} finally {
pm.close();
}
}
public Person updateProfileForCurrentUser(Person p)
{
p.setEmailAddress(getCurrentUserEmail());
return update(p);
}
public Person update(Person p)
{
PersistenceManager pm = getPersistenceManager();
try {
pm.currentTransaction().begin();
Key k = getKeyForEmailAddress(p.getEmailAddress());
p.setKey(k);
pm.makePersistent(p);
pm.currentTransaction().commit();
return p;
} finally {
if (pm.currentTransaction().isActive()) {
pm.currentTransaction().rollback();
}
pm.close();
}
}
private Key getKeyForEmailAddress(String emailAddress)
{
return KeyFactory.createKey(Person.class.getSimpleName(), emailAddress);
}
private static PersistenceManager getPersistenceManager()
{
// PMF is a singleton class that returns an instance of PersistenceManagerFactory
return PMF.get().getPersistenceManager();
}
When you have a List in a Persistence object, the load of the list are a lazy operation. So if you close the PersistenceManager before load the list, the list will be not loaded.
Try using:
public Person getProfileForCurrentUser()
{
PersistenceManager pm = getPersistenceManager();
try {
Key k = getKeyForEmailAddress(getCurrentUserEmail());
Person p = pm.getObjectById(Person.class, k);
p.getLocationStamps().size();
return p;
} catch (JDOObjectNotFoundException e) {
// Create a new profile if the new one isn't found
return updateProfileForCurrentUser(new Person());
} finally {
pm.close();
}
}

Simple object persist in Spring + hibernate

I suppose it is not standard way of doing that so any tips will be helpful, here is my code:
#RequestMapping("/register")
public String register(Map<String, Object> map, #ModelAttribute("user") MyUser user) {
if(user.getLogin() == ""){
map.put("user", new MyUser());
}
else{
map.put("user", user);
map.put("result", userService.addMyUser(user));
}
return "register";
}
what cause following error:
org.hibernate.AssertionFailure: null id in org.mypackage.MyUser entry
(don't flush the Session after an exception occurs)
Here is MyUser class:
#Entity
#Table(name="MyUser")
public class MyUser{
#Id
#Column(name="idMyUser")
#GeneratedValue
private Integer id;
#Column(name="login")
private String login;
#Column(name="password")
private String password;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Try changing the strategy and/or generator for the #GeneratedValue, see here and here for details (for example, you could try #GeneratedValue(strategy = GenerationType.IDENTITY). You could also check if your database table is set to generate the primary key values. The exception seems to indicate that the primary key -field is left unset by the current strategy and/or generator.

Relationships question in hibernate

I'm learning Hibernate and Play framework (also add Java into account...). I'm having problems saving this kind of entity
#Entity
#Table(name="users")
public class User extends Model {
#Required
public String username;
#Column(name="user_displayname",nullable=true)
public String displayname;
#Password
public String user_password;
#Email
#Column(name="user_email",nullable=false,unique=true)
public String user_email;
public String user_salt;
public Date user_joindate;
#ManyToOne
#JoinTable(name="users_meta")
public UserMeta userdata;
#Required
public boolean user_isActive;
#OneToOne(targetEntity=UserPhotos.class,cascade=CascadeType.ALL)
#JoinColumn(name="id",referencedColumnName="userID")
public UserPhotos userPhoto;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="links_rol2user")
public List<Rol> rol;
public User (String username, models.Pass password, String user_email) {
this.username = username;
this.user_password = password.getHashedPassword();
this.user_salt = password.getUserHash();
this.user_email = user_email;
this.user_joindate = new Date();
this.user_isActive = false;
}
This is my code when I'm registering a user
// check if the validation has errors
if(validation.hasErrors()) {
params.flash(); // add http parameters to the flash scope
validation.keep(); // keep the errors for the next request
register();
} else {
Cache.delete(uuid);
Pass pass = new Pass(password,new Date().toString());
User newUser = new User(firstName, pass, email);
UserMeta utest = new UserMeta(newUser.id);
utest.setUserTownID(pueblos);
newUser.setUserMeta(utest);
newUser.save();
Logger.info("NewUser ID : %s", newUser.getId());
// UserMeta userInfo = new UserMeta(newUser.getId());
// userInfo.setUserTownID(pueblos);
// userInfo.save();
// TODO salvar foto a null
// Confirmation left
Cache.set("thankyou", "alright!", "3mn");
thankyou();
}
I'm trying to save the userMeta, it does creates a new record when I set the userMeta object into newUser (not visible right now), but it doesn't insert the new ID created in newUser.
What kind of relation do I need? before I tweaked the code as it is now, it was a OneToOne relationship, worked quite well, but now when I was completing the register functions it kinda hit me that I needed to save userMeta object too..
If you need more info let me know, I don't know if I explained it well or not, just trying to get the hang of how Hibernate do relations, etc.
Adding UserMeta:
*/
#Entity
#Table(name="users_meta")
public class UserMeta extends Model {
#Lob
#Column(name="userBio")
public String userBio;
#Column(name="userPhotoID",nullable=true)
public Long userPhotoID = null;
#Column(name="userRoleID", nullable=false)
public Long userRoleID = 2L;
#Lob
public String userDescription;
#Column(name="userViews", nullable=false)
public Long userViews = 0L;
#Column(name="userFavoriteCount", nullable=false)
public Long userFavoriteCount = 0L;
#Column(name="userTotalComments", nullable=false)
public Long userTotalComments = 0L;
#Column(name="userTotalUploadedVideos", nullable=false)
public Long userTotalUploadedVideos = 0L;
public Long userTownID;
public Long userID;
public UserMeta() {}
public UserMeta(Long userid) {
this.userBio = "El usuario no ha escrito nada todavia!";
this.userDescription = "El usuario todavia no se ha describido!";
this.userID = userid;
}
public Long getUserTownID() {
return userTownID;
}
public void setUserTownID(Long userTownID) {
this.userTownID = userTownID;
}
}
// pass model
public class Pass {
protected String hashed;
protected String userHash;
public Pass(String passwordToHash, String salt) {
StringBuffer passSalt = new StringBuffer(passwordToHash);
this.userHash = DigestUtils.md5Hex(salt);
passSalt.append(this.userHash);
passSalt.append(Play.configuration.getProperty("application.passwordSalt"));
this.hashed = DigestUtils.sha512Hex(passSalt.toString());
}
public String getHashedPassword() {
return this.hashed;
}
public String getUserHash() {
return this.userHash;
}
}
There seems to be a lot going on there! But from what I can tell, you problem is with the id that you are passing into the UserMeta.
As you are extending Model, the id is being generated by the Model class. However, this is not set until after the entity is saved to the database (as the id is auto-generated by the database).
Therefore, because you are passing the id into the UserMeta before the User object is saved, the value of id will be null.
If you can save the User object before you create your UserMeta object, your code should work.

Categories

Resources