Grails service not saving Domain Object When triggered by Message Queue - grails

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.

Related

Grails rejectValue - multiple checks causing ob.errors null

My domain object booking has multiple attributes that are allowed to be null, because they will be set later after the object has been saved to the db.
Part of myService.action():
booking.properties = params
if (booking.contactFirstname?.length() <= 1) { booking.errors.rejectValue("contactFirstname", "empty") }
if (booking.contactLastname?.length() <= 1) { booking.errors.rejectValue("contactLastname", "empty") }
if (booking.contactPhone?.length() <= 1) { booking.errors.rejectValue("contactPhone", "empty") }
if (booking.contactMobile?.length() <= 1) { booking.errors.rejectValue("contactMobile", "empty") }
if (booking.contactEmail?.length() <= 1) { booking.errors.rejectValue("contactEmail", "empty") }
if (booking.hasErrors() || ! booking.validate()) {
return [success: false, model: booking]
} else {
booking.save(failOnError: true)
return [success: true, model: booking]
}
My controller does:
def result = myService.action(params)
if (result.success) {
flash.success = message(code: "msg.successfullySaved")
redirect(action: "registerEventConfirmation", id: result.model.uid, params: [lang: params.lang], mapping: "paginated")
} else {
flash.error = message(code: "msg.errorSavingCheckFields")
render(view: "registerEventStep3", params: [lang: params.lang], model: [booking: result.model])
I'm using
hasErrors(bean: booking,field:'contactFirstname', 'has-error')}
to mark error fields.
If I now submit the form without any values in textfields, all fields are red, booking.errors has >0 errors.
If I submit the form after with a firstname, booking.errors is NULL and no other field is marked.
Is this a Bug? I'm with Grails 2.3.6
additional information
I visit the form, submit it empty completely
I see all form fields in red, object.errors has >0 errors (VALID)
I enter a value in the first field, firstname and submit
I see none of the form fields in red, object.errors =0 errors (INVALID)
I re-submit the form with none changes
I see all empty form fields in red, object.errors has >0 errors (VALID)
Now that I fully understand the situation and since I was having trouble sleeping I thought I give you a very concise answer so that you can hopefully make full sense and use things properly.
Firstly I know creating a validation bean sounds like it will be a lot of work so let me teach you how to do it all relatively simply and why it is my preferred method.
It is my preferred method simply because when you do
class MyController {
def myAction(Mybean bean) {
// 1. the object allowed into this save action
// are all that is available objects withing MyBean.
// If it has user define but not telephone. Then
// if telephone is passed to myAction it will fail and not recognise
// field
// When declaring Date someField or User user then the params now
// received as bean this way is now actually properly bound
// to the data / domainType declared.
// Meaning user will now be actual user or someField actually Date
}
So now to explain how to best solve this issue. When creating beans simply copy over the actual domain class from your domain folder into src/groovy/same/package in grails 2 or src/main/groovy/same/package in grails 3
Change name / class or copy as from Booking to BookingBean so it has a different name.
Add #Validateable above actual BookingBean in grails 2 or add implements to main class like Class BookingBean implements Validateable { in grails 3
Now since it is copied all the objects are identical and at this point a save from the controller would be
class MyController {
def myAction(BookingBean bean) {
Booking booking = new Booking()
// this will save all properties
booking.properties = bean
booking.save()
}
}
But you have a special circumstance and you wanted to declare a transient field in the main domain class what I would do instead is
class BookingBean {
def id
String contactFirstname
String contactLastname
boolean secondSave=false
static constraints = {
id(nullable: true, bindable: true)
contactFirstname(nullable:true) //,validator:checkHasValue)
contactLastname(nullable:true) //,validator:checkHasValue)
secondSave(nullable:true,validator:checkHasValue))
}
//use the same validator since it is doing identical check
static checkHasValue={value,obj,errors->
// So if secondSave has a value but contactFirstName
// is null then complain about contactFirstName
// you can see how secondSave gets initialise below
//typical set this to true when you are about to save on 2nd attempt
//then when set run validate() which will hit this block below
// Check all the things you think should have a
// value and reject each field that don't
if (val) {
if ( !obj.contactFirstname) {
errors.rejectValue('contactFirstname',"invalid.contactFirstname")
}
if ( !obj.contactSecondname) {
errors.rejectValue('contactSecondname',"invalid.contactSecondname")
}
//and so on
}
}
So now in your controller:
class MyController {
def save1(BookingBean bean) {
Booking booking = new Booking()
// this will save all properties
booking.whatEver = bean.whatEver
booking.save()
// you can choose to validate or not here
// since at this point the secondSave has
// not been set therefore validation not called as yet in the bean
}
//you probably have id and it should bind with actual domain class
def save2(BookingBean bean) {
booking.secondSave=true
if (!bean.validate()) {
//this is your errors
//bean.errors.allErrors
return
}
//otherwise out of that loop since it hasn't returned
//manually set each object
booking.contactFirstname=bean.contactFirstName
booking.contactSecondname=bean.contactSecondname
booking.save()
}
}
e2a side note - above should answer
well don't validate it until you have created it. Only validate it after you created the object then added a value. Alternative create a function possibly in a validation bean that you run as part of your 2nd check. This Example bean is not validated until formatRequest is called as seen here
I don't grasp the specifics of your question, so I will give some general guidance since I have just dug into this.
Don't call hasErrors() before validate(). If you do, Grails won't hand you errors from domain constraints and you will only end up with the errors you set yourself using rejectValue().
Be careful with using rejectValue(). Try to set all your errors using domain constraints. If you have sophisticated constraints use the validator syntax and obj.getPersistentValue() might be your friend once in a while.
If you still have to use rejectValue(), understand that any later calls to validate() will start from scratch and erase your prior errors. I have written a workaround for this (to be placed in your domain object) although I can't assure you it is 100% ok:
def validateWithErrors(def fields = null) {
def existingErrors = this.errors
def ret = (fields ? this.validate(fields) : this.validate())
existingErrors?.allErrors?.each { error ->
this.errors.rejectValue(error.field, error.code)
}
return (existingErrors?.allErrors ? false : ret)
}

Error during merge after async task

I have an application which basically calls multiple webservices, stores the data received from those webservices and renders it to the user. I have an async task that call all the webservices and it looks something like this:
List<Promise> t = new ArrayList<Promise>()
def TIMEOUT_TIME = 6
t[0] = task {
def responseFrom0 = webserviceRequestService.getResponse(0)
if(responseFrom0){
return responseFrom0
}
}
t[1] = task {
def responseFrom1 = webserviceRequestService.getResponse(1)
if(responseFrom1){
return responseFrom1
}
}
The action getResponse looks something like this:
List<ResponseResult> result = new ArrayList<TravelQuote>()
try {
wsClient = prepareRequestMap()
wsResponse = externalWebservice.getQuotes(wsClient)
wsResponse.responseList.each {
ResponseResult responseResult = new ResponseResult()
//populate properties of ResponseResult
responseResult.save(failOnError:true, flush:true)
result.add(responseResult)
}
} catch(Exception e){
log.error e.message
e.printStackTrace()
}
return result
And at the end, I collect all the responses from all webservices like this:
result.each {
if(it){
try{
it=it.merge()
}catch (Exception e){
log.error("Error occured while merging responses... : ${e.message}")
}
}
}
Now, the issue here is I get this exception from the last block of code
not-null property references a null or transient value: ResponseResult.dateCreated; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value: ResponseResult.dateCreated
The dateCreated comes from this class which I have implemented on all of my domain classes.
abstract class AbstractPersistentObject implements Serializable{
static final long serialVersionUID = 1L;
Date dateCreated
Date lastUpdated
}
The weird thing about this issue is, this happens only on production environment, no matter what I do, I cannot replicate this in any other environments. And also, once this happens, the server just throws that particular issue every time that code is run until the server is restarted. After a restart, this code works fine.
I am running out of ideas, anyone has any ideas?
The answer seems to be in the exception thrown: the external web service returns record(s) without value for dateCreated field.
In my experience data from production environments almost always contains missing or improperly formatted values. You should account for that by changing the definition of ResponseResult.dateCreated to allow null values and handle this scenario in your code.

grails executeUpdate flushing error in integration test

I have a service which updates a db column. The update is done using executeUpdate. In my test after the service method I'm trying to load the record. The record loads and every field is populated except for the one I just updated in the service.
To make it stranger, when I run the code normally through a browser it works perfectly. I can look in the database and see that the field is being persisted. It's only the integration test that doesn't work. It's got to be some type of hibernate session issue with the dirty field.
Here is my stripped down code. I left out the controller info. My test calls the controller, the controller calls the service.
class BasicProfile {
static hasMany = [ photos:Photo ]
}
class Photo {
BasicProfile basicProfile
String caption
String publicId
static belongsTo = [ basicPofile:profile ]
}
class PhotoService {
def updateCaption() {
...
Photo.executeUpdate("update Photo p set p.caption = ? where p.basicProfile.id = ? and p.publicId = ? ",
[newCaption, profile.id, publicId])
...
}
}
void testUpdateCaption() {
...
controller.updateCaption() //controller just calls the photoService method
//get json result from controller to load publicId
...
Photo photo = Photo.findByPublicId(publicId)
assertEquals "my new caption", photo.caption //photo.caption is null, but the rest of the photo object is populated properly from the database load
}
I've added a breakpoint on the assert so I can view the photo instance. It's a valid instance and every field is populated with the data from when it was created (prior to calling controller.updateCaption(). But after calling controller.updateCaption(), the caption field should have valid data, but it's still null (the default when the instance is created).
That's probably a cache of your domain instance, try this:
void testUpdateCaption() {
controller.updateCaption()
//force the query in a clean hibernate session
Photo.withNewSession {
def photo = Photo.findByPublicId(publicId)
assertEquals "my new caption", photo.caption
}
}

Grails 'false' unique error

I have the following domain classes (shortened version)
class TalkingThread {
static hasMany = [comments:Comment]
Set comments = []
Long uniqueHash
}
and
class Comment {
static belongsTo = [talkingThread:TalkingThread]
static hasOne = [author:CommentAuthor]
Long uniqueHash
static constraints = {
uniqueHash(unique:true)
}
}
and
class CommentAuthor {
static hasMany = [comments:Comment]
Long hash
String name
String webpage
}
the following methods
public TalkingThread removeAllComments(TalkingThread thread){
def commentsBuf = []
commentsBuf += thread.comments
commentsBuf.each{
it.author.removeFromComments(it)
thread.removeFromComments(it)
it.delete()
}
if(!thread.save()){
thread.errors.allErrors.each{
println it
}
throw new RuntimeException("removeAllComments")
}
return post
}
public addComments(TalkingThread thread, def commentDetails){
commentDetails.each{
def comment = contructComment(it,thread)
if(!comment.save()){
comment.errors.allErrors.each{ println it}
throw new RuntimeException("addComments")
}
thread.addToComments(comment)
}
return thread
}
Sometimes I need to remove all of the comments from a TalkingThread and add comments that share the same uniqueHashes. So I call the removeAllComments(..) method, and then the addComments(..) method. This causes a
Comment.uniqueHash.unique.error.uniqueHash which caused by a supposedly deleted comment and a 'fresh' comment being added.
Should I be flushing? Maybe there is something wrong with my domain classes?
Edit Expansion of question.
Maybe this is a different question, but I thought that the session has deleted all associations and objects. Therefore the session state is aware that all TalkingThread comments have been deleted. Of course this has not been reflected in the database. I also assumed that the 'saving' of new Comments would be valid given that such 'saving' is consistent with the session state. However such 'saving' would be inconsistent with the database state. Therefore, my understanding of how grails validates objects in relation to session and database state is flawed! Any help in understanding the process of validating saves with respect to session and database states would also be appreciated.
If you want to remove all the Comments from a TalkingThread then you can use Hibernate's cascade behaviour.
Add
static mapping = {
comments cascade: 'all-delete-orphan'
}
to TalkingThread and then you can call comments.clear() followed by thread.save() which will delete the comments that were in the association.
There's a good article on Grails one-to-many-relationships here. The official Grails docs on it are here.

Writing an Integration Test in Grails that will test DB persistence

My Integration-Test for my grails application is returning a null object when I try to get a domain object using grails dynamic get method.
This is a simplified example of my problem. Lets say I have a controller TrackerLogController that uses a service TrackerLogService to save an updated Log domain for another Tracker domain.
Domain Tracker:
class Tracker {
int id
String name
static hasMany = [logs: Log]
}
Domain Log:
class Log {
int id
String comment
static belongsTo = [tracker: Tracker]
}
Controller TrackerLogController save:
def TrackerLogService
def saveTrackerLog() {
def trackerId = params.trackerId
def trackerInstance = Tracker.get(trackerId)
Log log = TrackerLogService.saveTrackerLogs(trackerInstance, params.comment)
if( log.hasErrors() ){
//render error page
}
//render good page
}
Service TrackerLogService save:
Log saveTrackerLogs( Tracker tracker, String comment) {
Log log = new Log(tracker: tracker, comment: comment)
log.save()
return log
}
So now I want to write an Integration-Test for this service but I'm not sure if I should be writing one just for the simple logic in the controller (if error, error page else good page) I would think I would write a Unit test for that, and an Integration-Test to check the persistence in the Database.
This is what I have for my Integration-Test:
class TrackerLogServiceTests {
def trackerLogService
#Before
void setUp(){
def tracker = new Tracker(id: 123, name: "First")
tracker.save()
//Now even if I call Tracker.get(123) it will return a null value...
}
#Test
void testTrackerLogService() {
Tacker trackerInstance = Tracker.get(123) //I have tried findById as well
String commit = "This is a commit"
//call the service
Log log = trackerLogService.saveTrackerLogs(trackerInstance , commit)
//want to make sure I added the log to the tracker Instance
assertEquals log , trackerInstance.logs.findByCommit(commit)
}
}
So for this example my trackerInstance would be a null object. I know the Grails magic doesn't seem to work for Unit tests without Mocking, I thought for Intigration-Tests for persistence in the DB you would be able to use that grails magic.
You can't specify the id value unless you declare that it's "assigned". As it is now it's using an auto-increment, so your 123 value isn't used. It's actually ignored by the map constructor for security reasons, so you'd need to do this:
def tracker = new Tracker(name: "First")
tracker.id = 123
but then it would get overwritten by the auto-increment lookup. Use this approach instead:
class TrackerLogServiceTests {
def trackerLogService
private trackerId
#Before
void setUp(){
def tracker = new Tracker(name: "First")
tracker.save()
trackerId = tracker.id
}
#Test
void testTrackerLogService() {
Tacker trackerInstance = Tracker.get(trackerId)
String commit = "This is a commit"
//call the service
Log log = trackerLogService.saveTrackerLogs(trackerInstance , commit)
//want to make sure I added the log to the tracker Instance
assertEquals log , trackerInstance.logs.findByCommit(commit)
}
}
Also, unrelated - don't declare the id field unless it's a nonstandard type, e.g. a String. Grails adds that for you, along with the version field. All you need is
class Tracker {
String name
static hasMany = [logs: Log]
}
and
class Log {
String comment
static belongsTo = [tracker: Tracker]
}

Resources