Grails, Promise API and two open sessions - grails

I am trying to clear out a collection and update it at the same time. It has children and finding the current items in the collection and deleting them asynchronously would save me a lot of time.
Step 1. Find all the items in the collection.
Step 2. Once I know what the items are, fork a process to delete them.
def memberRedbackCriteria = MemberRedback.createCriteria()
// #1 Find all the items in the collection.
def oldList = memberRedbackCriteria.list { fetchMode("memberCategories", FetchMode.EAGER) }
// #2 Delete them.
Promise deleteOld = task {
oldList.each { MemberRedback rbMember ->
rbMember.memberCategories.clear()
rbMember.delete()
}
}
The error message is: Illegal attempt to associate a collection with two open sessions
I am guessing that because I find the items, then fork, this creates a new session so that the collection is built before forking and a new session is used to delete the items.
I need to collect the items in the current thread, otherwise I am not sure what the state would be.

Note that using one async task for all the deletions is effectively running all the delete operations in series in a single thread. Assuming your database can handle multiple connections and concurrent modification of a table, you could parallelize the deletions by using a PromiseList, as in the following (note untested code follows).
def deletePromises = new PromiseList()
redbackIds.each { Long rbId ->
deletePromises << MemberRedback.async.task {
withTransaction {
def memberRedbackCriteria = createCriteria()
MemberRedback memberRedback = memberRedbackCriteria.get {
idEq(rbId)
fetchMode("memberCategories", FetchMode.EAGER) }
memberRedback.memberCategories.clear()
memberRedback.delete()
}
}
}
deletePromises.onComplete { List results ->
// do something with the results, if you want
}
deletePromises.onError { Throwable err ->
// do something with the error
}

Found a solution. Put the ids into a list and collect them as part of the async closure.
Note also that you cannot reuse the criteria as per http://jira.grails.org/browse/GRAILS-1967
// #1 find the ids
def redbackIds = MemberRedback.executeQuery(
'select mr.id from MemberRedback mr',[])
// #2 Delete them.
Promise deleteOld = task {
redbackIds.each { Long rbId ->
def memberRedbackCriteria = MemberRedback.createCriteria()
MemberRedback memberRedback = memberRedbackCriteria.get {
idEq(rbId)
fetchMode("memberCategories", FetchMode.EAGER) }
memberRedback.memberCategories.clear()
memberRedback.delete()
}
}
deleteOld.onError { Throwable err ->
println "deleteAllRedbackMembers An error occured ${err.message}"
}

Related

Issue with CRMContainer in Twilio Flex

I built a simple plugin that shows in the CRMContainer the url of my CRM given some attributes parameters (if they are passed by), during inbound tasks this works fine, but the problem is that during outbound calls the behaviour is not the one expected, this is the piece of code:
flex.CRMContainer.defaultProps.uriCallback = (task) => {
return task
? `https://mycrm.zzz/${task.attributes.clicar}/${task.attributes.contacth}/`
: 'https://mycrm.zzz/contacts/';
}
}
I would need an additional condition that tells the code, if this is an outbound voice call to always show a default url.
I tried adding an if/else that checks if task.attributes.direction is outbound, but Flex says this is undefined.
Any tip?
Thanks
Max
The problem is that you aren't checking for the existence of the task. Your original code had this:
flex.CRMContainer.defaultProps.uriCallback = (task) => {
return task
? `https://mycrm.zzz/${task.attributes.clicar}/${task.attributes.contacth}/`
: 'https://mycrm.zzz/contacts/';
}
}
Which returns the URL with the task attributes in it only if the task exists, because of the ternary conditional.
So, when you try to use the attributes you need to make sure the task exists. So taking your code from the last comment, it should look like this:
flex.CRMContainer.defaultProps.uriCallback = (task) => {
if (task) {
if (task.attributes.direction === 'outbound'){
return `https://mycrm.zzz/${task.attributes.clicar}/${task.attributes.contacth}/`;
} else {
return `https://mycrm.zzz/contacts/`
}
} else {
return 'https://mycrm.zzz/contacts/';
}
}

Loop through array, checking each element with networking call/callback function, and then go to default if desired element is not found

Because of the very specific nature of this question, I could not find an answer anywhere. Basically I want to create a messaging conversation with a specific user, but only if a conversation with that user doesn't already exist. I am looping through an array of conversations, and for each conversation I fetch the identity of the other user via a call to my backend. However, if no conversation is found with a particular user, then I want to create a new conversation. This is what I am doing:
for convo in convos {
HTTPManager.getOtherUserFromConversation(conversation: convo, success: { (otherUser) in
if desiredUser == otherUser {
//Found the desired conversation, so bring the user
//to it instead of creating a new one
}
}, failure: {
//Networking failure
})
}
//IF WE DIDN'T FIND IT, CREATE A NEW CONVERSATION HERE
I have thought of making a boolean value called "found" and setting it to true if we find the desired conversation, but I don't know how to wait until the last callback has executed before checking this boolean in order to avoid calling the check too early. Can anyone point me in the right direction?
The classic solution for this is using dispatch-group
https://developer.apple.com/documentation/dispatch/dispatchgroup
There are many code examples for this. The idea is that each network call should be in a separate task and the system lets you know when all tasks are done (this is where you check "found").
How about create callback function that will called when the all the request to check the convos is done, or when the correct convo is found.
func checkConvoIfExist(convos: [Convo]){
var found = false
var countCheck = 0
for convo in convos {
HTTPManager.getOtherUserFromConversation(conversation: convo, success: { (otherUser) in
countCheck += 1
if desiredUser == otherUser {
//Found the desired conversation
found = true
callbackCheckConvo(result: found, convo: convo)
break // to stop the loop
}else{
if (countCheck == convos.count){
callbackCheckConvo(result: found)
}
}
}, failure: {
//Networking failure
countCheck += 1
if (countCheck == convos.count){
callbackCheckConvo(result: found)
}
})
}
}
func callbackCheckConvo(result: Bool, convo: Convo = nil){
if (result){
//found the desired conversation, so bring the user to it instead of creating a new one
}else{
//convo not found, create new one
}
}

BatchInserters.batchDatabase fails - sometimes - silently to persist node properties

I use BatchInserters.batchDatabase to create an embedded Neo4j 2.1.5 data base. When I only put a small amount of data in it, everything works fine.
But if I increase the size of data put in, Neo4j fails to persist the latest properties set with setProperty. I can read back those properties with getProperty before I call shutdown. When I load the data base again with new GraphDatabaseFactory().newEmbeddedDatabase those properies are lost.
The strange thing about this is that Neo4j doesn't report any error or throw an exception. So I have no clue what's going wrong or where. Java should have enough memory to handle both the small data base (Database 2.66 MiB, 3,000 nodes, 3,000 relationships) and the big one (Database 26.32 MiB, 197,267 nodes, 390,659 relationships)
It's hard for me to extract a running example to show you the problem, but I can do if this helps. Here the main steps I do though:
def createDataBase(rules: AllRules) {
// empty the data base folder
deleteFileOrDirectory(new File(mainProjectPathNeo4j))
// Create an index on some properties
db = new GraphDatabaseFactory().newEmbeddedDatabase(mainProjectPathNeo4j)
engine = new ExecutionEngine(db)
createIndex()
db.shutdown()
// Fill the data base
db = BatchInserters.batchDatabase(mainProjectPathNeo4j)
//createBatchIndex
try {
// Every function loads some data
loadAllModulesBatch(rules)
loadAllLinkModulesBatch(rules)
loadFormalModulesBatch(rules)
loadInLinksBatch()
loadHILBatch()
createStandardLinkModules(rules)
createStandardLinkSets(rules)
// validateModel shows the problem
validateModel(rules)
} catch {
// I want to see if my environment (BIRT) is catching any exceptions
case _ => val a = 7
} finally {
db.shutdown()
}
}
validateModel is updating some properties of already created nodes
def validateModule(srcM: GenericModule) {
srcM.node.setProperty("isValidated", true)
assert(srcM.node == Neo4jScalaDataSource.testNode)
assert(srcM.node eq Neo4jScalaDataSource.testNode)
assert(srcM.node.getProperty("isValidated").asInstanceOf[Boolean])
When I finally use Cypher to get some data back
the properties set by validateModel are missing
class Neo4jScalaDataSet extends ScriptedDataSetEventAdapter {
override def beforeOpen(...) {
result = Neo4jScalaDataSource.engine.profile(
"""
MATCH (fm:FormalModule {isValidated: true}) RETURN fm.fullName as fullName, fm.uid as uid
""");
iter = result.iterator()
}
override def fetch(...) = {
if (iter.hasNext()) {
for (e <- iter.next().entrySet()) {
row.setColumnValue(e.getKey(), e.getValue())
}
count += 1;
row.setColumnValue("count", count)
return true
} else {
logger.log(Level.INFO, result.executionPlanDescription().toString())
return super.fetch(dataSet, row)
}
}
batchDatabase indeed causes this problem.
I have switched to BatchInserters.inserter and now everything works just fine.

Grails async - creating promises

I am in grails 2.3.1 - trying to use the async features.
This is bulk data processing. I am trying to synchronise 2 databases, which involves comparing both and returning a list of 'deltas'. I am trying to speed up the process
The documentation says I can just add a set of closures to a PromiseList and then call onComplete() to check that all the closures have completed. These are my attempts - directly building on "You can also construct a PromiseList manually" in the documentation:
def tasksMemberDeltas = new PromiseList()
pages.each {Integer page ->
tasksMemberDeltas << {findCreateMemberDeltas(page, (page + pageSize) - 1)}
if (page % 30 == 0) {
tasksMemberDeltas.onComplete {
tasksMemberDeltas = new PromiseList()
}
}
Returns:
Error groovy.lang.MissingMethodException:
No signature of method: java.util.ArrayList.onComplete()
In the end I called .get() which calls waitAll. Going into .get() and finding that it did waitAll was my revelation.
So if I have a single task I call:
waitAll finalDeltas
If I have a list I call:
taskFinalDeltas.get()
onComplete() logically relates to a single delta. Not the list. So this works OK:
Promise memberDeleteDeltas = task {
findDeleteAndTagDeltas()
}
memberDeleteDeltas.onError { Throwable err ->
println "An error occured ${err.message}"
}
memberDeleteDeltas.onComplete { result ->
println "Completed create deltas"
}
waitAll(memberDeleteDeltas)

Capturing twitter status timeline updates/infinite scroll updates

I'm using KRL to inject elements into twitter timeline statuses similar to Jesse Stay's TwitterBook. The problem I have is that these elements are only associated with statuses that are currently visible when the bookmarklet is initiated. If a new status is added through the 'new tweet' updated via Ajax or through status updates via infinite scroll, these new statuses are untouched.
Is there a way to either poll for new statuses or sense a twitter status update event via KRL in order to inject elements only into those newly added statuses?
The example posted at
http://kynetxappaday.wordpress.com/2010/12/25/day-21-modifying-facebook-stream-with-kynetx/
works with the Facebook stream but the concept is the same
create setTimeout infinite loop to look for stream items
only select stream items not marked as processed
process stream items
rinse and repeat
Code example from post
ruleset a60x512 {
meta {
name "MikeGrace-status-update-translator"
description <<
MikeGrace-status-update-translator
>>
author "Mike Grace"
logging on
}
global {
datasource insult:HTML <- "http://www.pangloss.com/seidel/Shaker/index.html?" cachable for 1 second;
}
rule find_status_updates_by_mike_grace {
select when pageview ".*"
{
notify("Starting to look for status upates by Mike Grace","");
emit <|
// get app object to raise web events
app = KOBJ.get_application("a60x512");
// function that finds FB status updates by Mike Grace
function findMikeGrace() {
// loop through each stream item on the page that hasn't been processed already by the app
$K("li[id^=stream_story]:not(li[kfbt])").each(function() {
var currentStreamItem = this;
// grab the current stream item posters name
var name = $K(currentStreamItem).find(".actorName").text();
// mark the stream item as being processed to reduce future processing times
$K(currentStreamItem).attr("kfbt","y");
// is the stream item by the perpetrator?
if (name == "Michael Grace") {
// strikethrough the original update
$K(currentStreamItem).find(".messageBody").wrap("<strike />");
// get selector to return translation of status update
var returnSelector = $K(currentStreamItem).attr("id");
returnSelector = "li#"+returnSelector+" .messageBody";
// raise web event to get translation for non geeks
app.raise_event("get_insult", {"returnSelector":returnSelector});
} // end of checking name
}); // end of looping through unprocessed stream items
// call myself again later to process new items on the page
setTimeout(function() {
findMikeGrace();
}, 9000);
}
// start the process of finding the perpetrator
findMikeGrace();
|>;
}
}
rule get_insult {
select when web get_insult
pre {
selector = event:param("returnSelector");
insulter = datasource:insult("#{selector}");
foundInsult = insulter.query("font");
singleInsult = foundInsult[0];
}
{
emit <|
console.log(singleInsult);
$K(selector).parent().after("<br/>"+singleInsult);
|>;
}
}
}

Resources