Error during merge after async task - grails

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.

Related

Mass Printing on Generic Inquiry

I have created a generic inquiry in Acumatica that returns work orders that are open. We know that there is a print action that can be applied however, we want to print out multiple instances of the work orders. So we do not have to print them individually. Is there currently a solution for mass printing or can someone point me in the right direction of maybe a work around? I know there is a mass action print all function that can be found however, it does not do anything.
Just like in any other action redirecting users to the generated report(s), the Service Orders' Print Service Order button throws a PXReportRequiredException to open generated report in new window:
public class ServiceOrderEntry : PXGraph<ServiceOrderEntry, FSServiceOrder>
{
...
public PXAction<FSServiceOrder> printServiceOrder;
[PXUIField]
[PXButton]
public virtual void PrintServiceOrder()
{
if (this.IsDirty) Actions.PressSave();
if (ServiceOrderRecords.Current == null) return;
Dictionary<string, string> serviceOrderParameters =
GetServiceOrderParameters(ServiceOrderRecords.Current, false);
if (serviceOrderParameters.Count != 0)
{
throw new PXReportRequiredException(serviceOrderParameters,
"SD641000", PXBaseRedirectException.WindowMode.NewWindow, string.Empty);
}
}
...
}
If you add Service Orders' Print Service Order action as a mass action to Generic Inquiry (as shown on the screenshots below), the GI mass action will generate and open Service Order report form only for the first selected Service Order. Processing of any consequent Service Order will not be possible due to the PXReportRequiredException thrown to show the report form generated for the first Service Order.
In order to merge Service Order report forms generated for several Service Orders into a single report, you should create a custom processing screen and use the AddSibling method on a PXReportRequiredException instance. The AddSibling method will append the reports generated for 2nd+ Service Order to the PXReportRequiredException instance initially created for the first processed Service Order. After all selected Service Orders has been processed, a single PXReportRequiredException will be thrown to redirect the user to Report Viewer displaying all generated reports at once.
public class PrintServiceOrderProcess : PXGraph<PrintServiceOrderProcess>
{
public PXCancel<FSServiceOrder> Cancel;
public PXProcessing<FSServiceOrder> ServiceOrderRecords;
public PrintServiceOrderProcess()
{
ServiceOrderRecords.SetProcessDelegate(list => PrintServiceOrders(list));
}
public static void PrintServiceOrders(IEnumerable<FSServiceOrder> list)
{
PXReportRequiredException ex = null;
foreach (var order in list)
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
string srvOrdType = SharedFunctions
.GetFieldName<FSServiceOrder.srvOrdType>(true);
string refNbr = SharedFunctions
.GetFieldName<FSServiceOrder.refNbr>(true);
parameters[srvOrdType] = order.SrvOrdType;
parameters[refNbr] = order.RefNbr;
if (ex == null)
{
ex = new PXReportRequiredException(parameters, "SD641000", "SD641000");
}
else
{
ex.AddSibling("SD641000", parameters, false);
}
}
if (ex != null) throw ex;
}
}

MVC Post Request how to get Request Content if it's different to that expected by the Default ModelBinder

I have this MVC WebApi action:
PostTrips(List<Trip> trips)
When a list of trips is sent through everything works fine. If, however, someone is trying to post incorrect data, e.g just an object {} then trips is null - this is fine, but I would like to log the data that the user tried to push.
I tried to get it using string requestData = Request.Content.ReadAsStringAsync().Result; but it can only be called once, and I guess the default model binder is calling it to try an map it to my List<Trip>, as when I call it, the result is always null, even though I know I'm passing something in.
Does anyone know of another way to get the posted data again?
I got around this my removing the parameter List<Trip> trips from the action so I had:
public async Task<HttpResponseMessage> PostTrips()
{
}
This bypasses the default model binder and allows you to get the unmodified request content using:
string requestContent = await Request.Content.ReadAsStringAsync();
You can then do what ever you need with this - I wanted to log the data for error tracking.
To create the actual List<Trip> trips I then used Newtonsoft.Json to deserialise the string into a list:
List<TravelTrackerTrip> appTrips = JsonConvert.DeserializeObject<List<TravelTrackerTrip>>(requestContent);
Full example:
public async Task<HttpResponseMessage> PostTrips()
{
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
List<Trip> appTrips = null;
string requestContent = await Request.Content.ReadAsStringAsync();
try
{
appTrips = JsonConvert.DeserializeObject<List<Trip>>(requestContent);
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
try
{
//Success - do whatever we need
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
//Return success response
}

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)
}

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]
}

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