How to make my audit trail plugin work - grails

I am in an need to use audit trail in my grails application i have tried all methods but audit log is empty is there any way to rectify it.I need to actually record operations such as insert,delete and update.
Below is what I followed:-
package audit
class Person {
static auditable = true
String firstName
static constraints = {
firstName(nullable:true,size:0..60)
}
def onSave = {
println "new person inserted"
}
def onUpdate = {
println "person was updated"
}
def onDelete = {
println "person was deleted"
}
def onChange = { oldMap,newMap ->
println "Person was changed ..."
oldMap.each{ key, oldVal ->
if(oldVal != newMap[key]) {
println " * $key changed from $oldVal to " + newMap[key]
}
}
}
}

Other listservs that I check have suggested that the current audit-logging plugin is buggy, so you may just be experiencing a bug in the plugin. Also, I believe it has been forked and is actively being rewritten (http://jira.grails.org/browse/GPAUDITLOGGING), so you may not want to spend too much time with it right now.
With that said, I scaffolded a simple application with the domain you provided and the plugin did write out the println statements, but it only recorded the updates correctly in the database to the AUDIT_LOG table. The 2 inserts I attempted recorded null for both the NEW_VALUE and PROPERTY_NAME.

Related

grails 2.3.x: Strange behavior with autogenerated controller

A fresh grails 2.3.x project. Im using autogenerated controller and views for a domain class. One (not null) property i want to set directly in controller, so i removed the input field from the _form.gsp.
Domain class:
class Demo {
String demo
String status
static constraints = {
demo nullable: false
status nullable: false
}
In form i only fill the demo field.
Autogenerated controller results in:
...
#Transactional
def save(Demo demoInstance) {
if (demoInstance == null) {
notFound()
return
}
// set the status property to "test"
demoInstance.status = "test"
println "1 STATUS: ${demoInstance.status}"
if (demoInstance.hasErrors()) {
println "2 STATUS: ${demoInstance.status}"
respond demoInstance.errors, view: 'create'
return
}
println "3 STATUS: ${demoInstance.status}"
...
Fill the form and leave the status property blank results in
println 1 -> status = 'test'
println 2 -> status = 'test'
Redirect to create page with a message: "Property [status] of class [Demo] cannot be null"
When i change the controller as in grails 2.2.x:
...
#Transactional
def save() {
def demoInstance = new Demo(params)
if (demoInstance == null) {
notFound()
return
}
// set the status property to "test"
demoInstance.status = "test"
println "1 STATUS: ${demoInstance.status}"
if (demoInstance.hasErrors()) {
println "2 STATUS: ${demoInstance.status}"
respond demoInstance.errors, view: 'create'
return
}
println "3 STATUS: ${demoInstance.status}"
...
This results in the expected output:
println 1 -> status = 'test'
println 3 -> status = 'test'
And the input is stored in Database.
Can anyone explain this behavior? Thanks.
I had the same issue. Do not know the exact reason, but what works is:
bindData(demoInstance, [status: 'test'])
Grails 2.3 has a new DataBinding. you can use the old Spring style if you set
grails.databinding.useSpringBinder = true
in Config.groovy
Using this SpringBinder, this issue does not occur.
I don't know why you're seeing the behavior you're seeing with the 2.2.x code, but the 2.3.x behavior looks correct to me regardless of which data binder you use. Data binding occurs before the controller action is called and since you left a property unset, the instance has a validation error. You then set a valid value but don't call validate() or save(), so there's no reason to expect that hasErrors() would change from false to true.
If you add
demoInstance.validate()
after
demoInstance.status = "test"
then I would expect to see the output you're seeing in your 2.2.x version (although again, without a validate or save call I can't imagine why it would work in 2.2 either).

Grails-null id in com.easytha.Student entry (don't flush the Session after an exception occurs)

I have already seen several threads for this issue and none could rescue
I have the following in my DomainClass
def afterInsert() {
elasticSearchService.index(this)
}
Where elasticsaerch is a service and I have added it to the static transient list. It seems that after calling the index method successfully it throws this exception
Message: null id in com.easytha.Student entry (don't flush the Session after an exception occurs)
This is the code of index method
def index(object) {
try {
if(object==null) {
throw new NullPointerException()
}
if(!(object instanceof Collection || object instanceof Object[])) {
IndexResponse response = client.prepareIndex(grailsApplication.config.esIndexName, object.getClass().getName(),object.id.toString())
.setSource((object as JSON).toString() )
.execute().actionGet()
println "object indexed successfully"
}else if(object instanceof Collection || object instanceof Object[]) {
for (var in object) {
index(var)
}
}
}catch(e) {
e.printStackTrace();
}
}
"object indexed successfully" is printed in the console.
The bootstrap.groovy has the following
Student student4 = new Student(firstName:'Sapan',lastName:'Parikh',email:'sapan.parikh#eclinicalworks.com',password:'test123')
student4.save(failOnError : true)
UPDATE
I tried Student.withNewSession { elasticSearchService.index(this) } which worked.
It's stabbing at things but maybe shift the save to happening within a transaction:
Student.withTransaction {
student4.save()
}
I've seen this pop up unexpectedly when doing things outside of services (that should, imo, be in services).
Summing up some subsequent discussion:
The student model was saved throughout the application, so it wasn't suitable to shift all the saves to services or wrap in transaction blocks. The OP notes that moving the original reindexing code into a new session fixed it everywhere.
def afterInsert() {
elasticSearchService.index(this)
}

Can't make Grails and audit-logging plugin work

I'm trying to add data auditing to my Grails project, but it doesn't work. I've read this tutorial on grails site and did everything as described there.
What I have now is a domain class Event looking as simple as this:
class Event {
static auditable = true;
String name;
def onSave = {
println "event inserted";
}
def onChange = {
println "event changed";
}
}
and a method in the main controller:
def addEvent = {
def obj = new Event([name:"Test event 1"]);
if (obj.save(flush:true)){
render "addEvent complete for " + obj.toString();}
else{
render "addEvent failed for " + obj.toString();
obj.errors.allErrors.each {
log.error( it.toString() );
}
}
}
I also added auditLog.verbose = true to the Config.groovy file.
Still when I call this method from my browser, the new event is created in DB, but there is nothing in the audit_log table, nor in the console about this happening.
What am I missing here?

Grails: sort order on one-to-many relationship not working

I've got a simple bi-directional one-to-many mapping, as follows, with a default sort order specified on the owning side. However, the sort order doesn't seem to be getting applied? I'm using Grails v2.0.1 (I've now replicated this example with v1.3.7).
package playground
class User {
String name
static hasMany = [ posts : Post ]
static mapping = {
posts sort:'position'
}
}
and
package playground
class Post {
int position = 1
String message
static belongsTo = [ user : User ]
}
this is the integration test code I'm using to exercise it ...
def User user = new User(name:'bob')
user.addToPosts(new Post(position:2, message:'two'))
user.addToPosts(new Post(position:3, message:'three'))
user.addToPosts(new Post(position:1, message:'one'))
assertTrue user.validate()
assertFalse user.hasErrors()
assertNotNull user.save()
for (post in user.posts) {
log.debug "Post message> ${post.message}"
}
Please put me out of my misery, it's presumably something obvious but I can't see it! Thanks.
use this code:
package playground
class User {
String name
static hasMany = [ posts : Post ]
static mapping = {
posts sort:'position' order:'desc'//order:'asc'
}
}
Turns out this is a bit of odd edge-case behaviour, that's really a result of the way the test is written. Basically, everything is happening in the scope of a single Hibernate session/txn (see conversations above). So the test is fetching back the object graph it just created (with the out of order Set), rather than fetching data from the database.
If you force a separate transaction then you get the behaviour you are looking for and the 'order by' works as expected. E.g.
User.withNewSession{ session ->
def User foundUser = User.get(user.id);
for (post in foundUser.posts) {
println "Post message> ${post.message}"
}
}
Code courtesy of ndtreviv.
If you use a list instead of a Set (the default) the framework will maintain the order for you.
List posts = new ArrayList()
static hasMany = [ posts : Post ]
Owens answer got me out of the confusion i was in. i was trying to the the ordering defined on a relationship between users (1) and posts (many), but when i wrote the initial tests they were failing as using User.get (u.id) was in the same session - and so was just reading out of the cache and they came back in the order i'd written tem not newest first as i'd expected.
I then rewrote the test across two sessions and low and behold in the second session this time returned the posts in desc order.
So you just have to be careful. if you are in the same original session that creates the posts then you have to use User.get (u.id).posts.sort().
All these little gotchas with not understanding properly how the session cache and underlying DB work in the scope of the same session/transaction. makes your brain ache sometimes.
whilst we are noting things - this errored in integration test in 3.2.5, but i spotted a thread by Jeff and the fix that had gone. So i upgraded to grails 3.2.6 last night and this test now works
void "test query"() {
given:"a user and where query for users, and posts "
User u
when: "create a post for user "
User.withNewSession { session ->
u = new User(username: 'will')
u.save(flush: true, failOnError: true)
Post p1 = new Post(comment: [food: "bought coffee and cake "])
Post p2 = new Post(comment: [dinner: "bought wine and dinner"])
Post p3 = new Post(comment: [view: "spectacular view of lake and sunset"])
u.addToPosts(p1)
u.addToPosts(p2)
u.addToPosts(p3)
u.save(flush: true)
if (u.hasErrors())
println "error saving posts on user u : ${u.errors}"
def postList = User.get(u.id).posts
postList.each { println "query via user.list using same session > $it.dateCreated : $it.comment" }
Post.findAll().each { println "query via Post using same session > $it.dateCreated : $it.comment" }
}
//because still in same session it just returns the order from the 1st level cache - so force a
//new session and let the DB do the sort
def lookupPosts
User.withNewSession{ session ->
User uNew = User.get(1)
assert uNew
lookupPosts = uNew.posts
lookupPosts.each {println "query via user in new session > $it.dateCreated : $it.comment" }
}
then: " check post was added"
!u.hasErrors ()
lookupPosts.size() == 3
lookupPosts[1].comment.dinner == "bought wine and dinner"
}

Grails service not saving Domain Object When triggered by Message Queue

I have a grails application that has a service that creates reports. The report is defined as:
class Report {
Date createDate
String reportType
List contents
static constraints = {
}
}
The service generates a report and populates contents as a list that is returned by createCriteria.
My problem is that my service claims to be saving the Report, no errors turn up, logging says that its all there, but when I go to call show from the controller on that report, it says contents is null.
Another relevant bit, my Service is called by an ActiveMQ message queue. The message originating from my report controller.
Controller:
class ReportController {
def scaffold = Report
def show = {
def rep = Report.get(params.id)
log.info("Report is " + (rep? "not null" : "null")) //says report is not null
log.info("Report content is " + (rep.contents? "not null" : "null")) //always says report.contents is null.
redirect(action: rep.reportType, model: [results: rep.contents, resultsTotal: rep.contents.size()])
}
}
My service that creates the report:
class ReportService {
static transactional = false
static expose = ['jms']
static destination = "Report"
void onMessage(msg)
{
this."$msg.reportType"(msg)
}
void totalQuery(msg)
{
def results = Result.createCriteria().list {
//This returns exactly what i need.
}
Report.withTransaction() {
def rep = new Report(createDate: new Date(), reportType: "totalQuery", contents: results)
log.info("Validation results: ${rep.validate()}")
if( !rep.save(flush: true) ) {
rep.errors.each {
log.error(it)
}
}
}
}
Is there something obvious that I'm missing here? My thought is that since all my unit tests work, that the hibernate context is not being passed through the message queue. But that would generate Exceptions wouldn't it? I've been beating my head on this problem for days, so a point in the right direction would be great.
Thanks,
You can't define an arbitrary List like that, so it's getting ignored and treated as transient. You'd get the same behavior if you had a def name field, since in both cases Hibernate doesn't know the data type, so it has no idea how to map it to the database.
If you want to refer to a collection of Results, then you need a hasMany:
class Report {
Date createDate
String reportType
static hasMany = [contents: Result]
}
If you need the ordered list, then also add in a List field with the same name, and instead of creating a Set (the default), it will be a List:
class Report {
Date createDate
String reportType
List contents
static hasMany = [contents: Result]
}
Your unit tests work because you're not accessing a database or using Hibernate. I think it's best to always integration test domain classes so you at least use the in-memory database, and mock the domain classes when testing controllers, services, etc.

Resources