Strange Grails behaviour populating List at bootstrap - grails

I'm becoming crazy on a strange Grails behaviour.
I get a domain class user :
class User {
String firstName
String token
List roles = [] as List
static constraints = {
}
}
I modify my BootStrap.groovy like this to populate 5 user instance :
class BootStrap {
def init = { servletContext ->
switch (Environment.getCurrent()) {
case 'DEVELOPMENT':
def user1 = new User(
id: 1,
firstName: 'Enricot',
token: 'L\'abricot'
)
user1.roles.add('ROLE_USER')
user1.save(failOnError: true)
def user2 = new User(
id: 2,
firstName: 'Arnaud',
token: 'Dauphin')
user2.roles.add('ROLE_USER')
user2.roles.add('PERM_WRITE')
user2.save(failOnError: true)
def user3 = new User(
id: 3,
firstName: 'Magalie',
token: 'La banane')
user3.roles.add('ROLE_USER')
user3.roles.add('PERM_READ')
user3.save(failOnError: true)
def user4 = new User(
id: 4,
firstName: 'Jeremy',
token: 'Wistiti')
user4.roles.add('ROLE_USER')
user4.roles.add('ROLE_ADMIN')
user4.save(failOnError: true)
def user5 = new User(
id: 5,
firstName: 'Jimini',
token: 'Criquet')
user5.roles.add('ROLE_USER')
user5.roles.add('ROLE_INTERACTIONS')
user5.save(failOnError: true)
break
case "test":
DataBuilder.init()
break
}
}
def destroy = {
}
}
In my controller, when I'm getting one user, all fields are corect, except the List.
If I do :
def user = User.get(1)
println user.firstName // Output is "Enricot"
println user.token // Output is "L'abricot"
println user.roles // Output is "[]" and not "[ROLE_USER]"
Trying populate object like this did not solve my problem :
def user1 = new User(
id: 1,
firstName: 'Enricot',
token: 'L\'abricot',
roles: ['ROLE_USER']).save(failOnError: true)
I'have try with double quote, simple quote.
The most funny part is when I'm making "user.roles" INTO BootStrap.groovy, I get the correct List.
It's making no sense for me, any help is welcome,

It's not strange behavior, you're doing it wrong :)
When you add a field that you want to be persistent, you need to give Grails information about how to store it. Just a simple List isn't enough information. If it's a list of other domain classes, it will create a foreign key and store the ids. If it's a list of Strings, it will store them as varchars. As shown in the documentation, you need to do something like this:
class User {
String firstName
String token
static hasMany = [roles: String]
}
This changes the backing collection to a Set, but this is likely correct for roles since you don't care about ordering and want uniqueness. If you do need ordering, add an uninitialized List field to signal to Grails that you don't want a Set:
class User {
String firstName
String token
List roles
static hasMany = [roles: String]
}
This changes how you populate the data. Instead of directly adding to the collection, use the dynamic addToRoles method that gets added for you:
class BootStrap {
def init = { ctx ->
environments {
development {
def user1 = new User(firstName: 'Enricot', token: 'L\'abricot')
user1.addToRoles('ROLE_USER')
user1.save(failOnError: true)
def user2 = new User(firstName: 'Arnaud', token: 'Dauphin')
user2.addToRoles('ROLE_USER')
user2.addToRoles('PERM_WRITE')
user2.save(failOnError: true)
def user3 = new User(firstName: 'Magalie', token: 'La banane')
user3.addToRoles('ROLE_USER')
user3.addToRoles('PERM_READ')
user3.save(failOnError: true)
def user4 = new User(firstName: 'Jeremy', token: 'Wistiti')
user4.addToRoles('ROLE_USER')
user4.addToRoles('ROLE_ADMIN')
user4.save(failOnError: true)
def user5 = new User(firstName: 'Jimini', token: 'Criquet')
user5.addToRoles('ROLE_USER')
user5.addToRoles('ROLE_INTERACTIONS')
user5.save(failOnError: true)
}
test {
DataBuilder.init()
}
}
}
}
See http://grails.org/doc/latest/guide/conf.html#environments for information about the environment block support in BootStrap.groovy.
Also note that I removed your explicit id values since you are not using assigned ids, so those values are ignored.
Finally, it looks like you're rolling your own security. Don't do this. Use a proven framework. Both Spring Security Core and Shiro are powerful and easy to get started with.

It's normal behaviour for grails. The code below just adds item to list in current object (Important! not to database reference):
user1.roles.add('ROLE_USER')
To add to database mapping to roles use dynamic method like this:
user1.addToRoles('ROLE_USER')
Assumption: you mean for your domain
List<Strings> roles
static hasMany = [roles:String]

Related

How can I use Spring Data Neo4j to create a Node with new Relationships to existing Nodes

My use case is that I want to send a Message from one User to another, by creating a Message node in between two Users. Here is the JSON I would like to send from the client, and my Message class:
{"text":"Hello!","recipient":{"userId":"1823498e-e491-45fa-95bb-782a937a00ba"},"sent":{"userId":"6467b976-7ff6-4817-98e0-f3fbfca1413e"}}
#Node("Message")
#Data
public class Message
{
#Id #GeneratedValue private UUID messageId;
#NotNull
private String text;
#Relationship(type = "SENT",direction = Direction.INCOMING)
private User sent;
#Relationship("TO")
private User recipient;
}
But when I call Mono<Message> save(Message message); in the ReactiveNeo4jRepository, the DB unique constraint complains that Users with those usernames already exist! It seems like it's trying to reset all of the other properties for those two userIds to empty/defaults, presumably since they're not included in the JSON. So I get a constraint error when it sees two "FAKE" usernames.
2022-03-26 14:34:29.120 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.o.OutboundMessageHandler : [0xb08eba68][x.server.com:7687][bolt-128] C: RUN "MERGE (user:`User` {userId: $__id__}) SET user += $__properties__ RETURN user" {__id__="6467b976-7ff6-4817-98e0-f3fbfca1413e", __properties__={lastName: NULL, credentialsNonExpired: FALSE,userId: "6467b976-7ff6-4817-98e0-f3fbfca1413e", enabled: FALSE, firstName: NULL, password: "FAKE",accountNonExpired: FALSE, email: NULL,username: "FAKE", accountNonLocked: FALSE}} {bookmarks=["FB:kcwQx2Hl5+KYQJiGdiyzOa4EWSCQ"]}
2022-03-26 14:34:29.206 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.i.InboundMessageDispatcher : [0xb08eba68][x.server.com:7687][bolt-128] S: SUCCESS {fields=["user"], t_first=2}
2022-03-26 14:34:29.207 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.o.OutboundMessageHandler : [0xb08eba68][x.server.com:7687][bolt-128] C: PULL {n=-1}
2022-03-26 14:34:29.301 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.i.InboundMessageDispatcher : [0xb08eba68][x.server.com:7687][bolt-128] S: FAILURE Neo.ClientError.Schema.ConstraintValidationFailed "Node(1) already exists with label `User` and property `username` = 'FAKE'"
UPDATE: I can accomplish what I need to using an ObjectMapper from Jackson and the Neo4j Driver directly, as follows. But I still would like to know how to this with SDN.
#Override
public Mono<UUID> save(Message message)
{
String cypher = "MATCH (a:User),(b:User) where a.userId = $fromUserId and b.userId = $toUserId CREATE (a)-[:SENT]->(m:Message {messageId: $messageId, text : $text})-[:TO]->(b)";
Map<String,Object> objMap = persistenceObjectMapper.convertValue(message,mapType);
return Mono.from(driver.rxSession().writeTransaction(tx -> tx.run(cypher,objMap).records())).then(Mono.just(message.getMessageId()));
}
It seems like it's trying to reset all of the other properties for those two userIds to empty/defaults
The problem you are facing is rooted in the handling of the incoming Message object.
As far as I understood this, you are directly saving the Message with the attached, de-serialized Users.
You have to fetch the Users from the database first. SDN does no pre-fetch. One way to do this is to create an additional repository for the User entity.
Given a MovieService that gets called by the controller, you would end up with something like this:
#Transactional
public Message saveNewMessage(Message newMessage) {
UUID recipientId = newMessage.getRecipient().getUserId();
UUID senderId = newMessage.getSent().getUserId();
User recipient = userRepository.findById(recipientId).orElseGet(() -> {
User user = new User();
user.setName("new recipient");
return user;
});
User sender = userRepository.findById(senderId).orElseGet(() -> {
User user = new User();
user.setName("new sender");
return user;
});
newMessage.setRecipient(recipient);
newMessage.setSent(sender);
return messageRepository.save(newMessage);
}
Tested this with:
CREATE CONSTRAINT username ON (user:User) ASSERT user.name IS UNIQUE
for 4.3 and/or for 4.4
CREATE CONSTRAINT username FOR (user:User) REQUIRE user.name IS UNIQUE
I created an example for your use case: https://github.com/meistermeier/neo4j-issues-examples/tree/master/so-71594275
There I also a user properties erasing call stack (/clearusers) that shows what I think happens in your application.
Edit: Since I usually advice not to create a repository for every entity class, you could also use the Neo4jTemplate. I have added the saveNewMessageAlternative to the example.

Grails 2.4.4 removing all items from a hasmany String relationship

I have a User class that hasMany organizations, and organizations are String UIDs.
class User implements Serializable {
...
List organizations
static hasMany = [organizations: String]
...
}
To update an User instance, I need to remove all the Strings from "organizations" before adding what the updated uids, but it doesn't work. The same organizations are resaved into the collection after I delete them and save the User instance.
I tried many ways:
// 1
user.organizations.clear()
// 2
def orgsToDelete = [] // this is to avoid concurrent modification exception
orgsToDelete += user.organizations
orgsToDelete.each { orguid ->
user.removeFromOrganizations(orguid)
}
After any of 1 or 2, the user.organizations is empty, but when I do:
user.save(flush:true)
I get the same organizations that where before the clear/removeFromOrganizations
I can't do user.organizations.each { it.delete() } because the items are not domain classes but Strings.
Another weird thing is I have a custom validator to check if the collection has any items, and it seems it doesn't gets any errors, event the organizations is empty, this is my validator:
organizations validator: { val, obj ->
if (obj.organizations.size() == 0) return false
return true
}
When I do this on the controller update action, it says hasErrors() == false
if (userInstance.hasErrors())
{
println "has errors"
respond userInstance.errors, view:'edit'
return
}
Any ideas?
Here is the controller: https://github.com/ppazos/cabolabs-ehrserver/blob/master/grails-app/controllers/com/cabolabs/security/UserController.groovy#L199-L237
Use your own domain class instead of String as collection's elements, like:
class Organization {
String name
static belongsTo = [ User ]
}
then you should be able to properly remove the children by
user.organisations.clear()
user.save()
The reason for the behaviour you a facing is, that the String instances are mapped to a table which has no back-refs to the user. That leads to the situation, that the records in that string table are not actually removed and are loaded upon the next database call.
I have read your project code and you have the DataSource bad set. You are using a H2 memory BBDD, but using the MySQL driver instead (in the development environment). It doesnt look good. Set it properly, and try again.
I have test your model, and used:
user.organizations.clear()
without any problems, even using your custom validator.
In other hand you should use a Service instead for all the business logic, not a controller. Because a service is transactional by default.
Here are the changes I did, and works:
// List organizations = []
static hasMany = [organizations: String]
static mapping = {
password column: '`password`'
// organizations lazy: false
}
Removing List organizations. Using the H2 datasource with this config at DataSource development environment:
dbCreate = "create-drop"
url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
username = "sa"
password = ""
And this code works like a charm:
def clearOrganizationFromUser(){
//creating user with timestamp
User user = new User(username: 'username' + new Date(), password: 'password', email: 'email#email.es', accountExpired: false, accountLocked: false,
passwordExpired: false)
List<String> organizations = []
10.times{
organizations << "organization $it"
}
organizations.each{ String organization ->
user.addToOrganizations(organization)
}
user.save()
println "Organizations after saving: ${user.organizations}"
//Its not neccesary but I did it for showing that organization are persisted in DDBB
User newUserFromDDBB = User.get(user.id)
//deleting organization
newUserFromDDBB.organizations.clear()
newUserFromDDBB.save()
println "---> Organizations after deleting: ${newUserFromDDBB.organizations}"
}
Hope It works. If do, please mark as solved.

Grails findBy belongsTo gives JdbcSQLException

Say, for a system, I had Student and User objects, like so:
User {
}
Student {
User user //not nullable
static belongsTo = [User]
}
Teacher {
static belongsTo = [user:User]
}
I have a lot of code written against these models, and plenty of dynamic finders in it
def u1 = new User().save( flush: true)
new Student( user: u1 ).save( flush: true)
...
def s = Student.findByUser( springSecurityService.currentUser)
//s can be null
def t = Teacher.findByUser( springSecurityService.currentUser)
//t can be null
if( s != null)
//do something with 's'
if( t != null)
//do something with 't'
...
And this all worked fine. But I wanted to update my domain model to add cascading access from User to Student. So I did:
User {
Student student //nullable
}
And I would save a user like this:
new User( student: new Student() ).save( flush: true)
But the problem is, when I try to access 's' like I did above.
If the Student.findByUser can find one such student, then it works.
But if it should return null, it gives an error instead.
def s = Student.findByUser( springSecurityService.currentUser)
will result in:
org.h2.jdbc.JdbcSQLException
Parameter #2 is not set; SQL statement:
select this_.id as id18_0_, this_.version as version18_0_, from student this_ where this_.id=? limit ? [90012-173]
I know I can access the student object through the user object like user.student, but I would like to be able to keep the existing code as it is.
I've seen this thread, and it had a similar problem, but no resolution grails email list
Is this a bug? What can I do to get around it? Am I not setting up my domain models properly?
Thanks
As an alternative, you could try something like this using hasOne instead of belongsTo.
class User {
static hasOne = [student:Student, teacher:Teacher]
static constraints = {
student unique:true, nullable: true
teacher unique:true, nullable: true
}
}
class Student {
User user
}
class Teacher {
User user
}
It still cascades and appears to handle null searches fine.

Grails find in relationship

I'm breaking my head into wall about this logic, I have 2 domains, User and RSS. When the user add a RSS, I have to compare if it's not duplicated URL in my db by given another URL in the same user.
class RSS {
Long id
String link
static belongsTo = [user:User]
}
class User {
Long id
Long uid //facebook
String name
static hasMany = [rss:RSS]
}
def addRSS(){
//logic url is valid or not
...
def user = User.findByUid(data.id) //get User uid and then by this uid, i can get the all RSS url
//and compare like if(db_url == given_url) ...
}
I tried many ways and I had no success.
You could also use one of the findOrSaveWhere or findOrCreateWhere methods
def url = 'some url from user' //data.url I would assume
def user = User.findByUid(data.id)
RSS.findOrSaveWhere(url: url, user: user)
If it's in the db it will fetch it for you if not it will create it for you. The documentation explains the difference between the *Save* and *Create*
Simple join would do I think:
def existing = RSS.withCriteria{
eq 'link', url
eq 'user.id', userId
}
or
def existing = RSS.withCriteria{
eq 'link', url
user{ eq 'uid', uidd }
}
if( existing ) return
else doSave()

httpSession in Grails

I need to access the domain class User of the current session. The following code works:
class RatingController {
def rate = {
def rating = params.rating
def artist = Artist.get( params.id )
def user = User.get(1)
user.addToRatings(new Rating(artist:artist, rating:rating))
user.save()
render(template: "/artist/rate", model: [artist: artist, rating: rating])
}
}
But instead of explicitly get the user with ID equal 1 (User.get(1)) I need to access the user of the current session. I tried the following code, but it doesn't work:
class RatingController {
def rate = {
def rating = params.rating
def artist = Artist.get( params.id )
def user = user.session
user.addToRatings(new Rating(artist:artist, rating:rating))
user.save()
render(template: "/artist/rate", model: [artist: artist, rating: rating])
}
}
I'm still struggling to fully understand the httpSession concept, so a little help would be great.
Thank in advance.
UPDATE
In my UserController, my authentication looks like this:
def authenticate = {
def user = User.findByLoginAndPassword(params.login, params.password)
if(user){
session.user = user
flash.message = "Hello ${user.name}!"
redirect(controller:"event", action:"list")
}else{
flash.message = "Sorry, ${params.login}. Please try again."
redirect(action:"create")
}
}
the http session is nothing more than data that is maintained among a sequence of requests from a single source, like a browser.
To put something on the session, just do
session.setAttribute("key", value)
and to get data off the session just do
session.getAttribute("key")
Grails also adds some fanciness to session access as outlined here. Their example shows
def user = session["user"]
session["user"] = "John"
asset "John" == session.user
Note that if you are using a grails plugin for authentication and authorization, it will probably provide a way to get the user. For example Spring Security gives you the following
springSecurityService.getCurrentUser()
depending on the details of your plugin, that user might or might not be on the session.
In your code I noticed you wrote
def user = user.session
When I think you mean
def user = session.user
The other thing I do is make it session.userId ie session.userId = user.id and then
def user = User.get(session.userId)
Not sure if that's necessary.
You'll need to use .merge():
user.merge(flush: true)
This is because the instance is detached from the persistence context when you save it to HttpSession. Here is the doc from grails:
http://grails.org/doc/2.3.1/ref/Domain%20Classes/merge.html

Resources