gorm.tools.async.AsyncService.groovy Maven / Gradle / Ivy
/*
* Copyright 2019 Yak.Works - Licensed under the Apache License, Version 2.0 (the "License")
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
package gorm.tools.async
import java.util.concurrent.CompletableFuture
import java.util.function.Consumer
import java.util.function.Supplier
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.grails.datastore.mapping.core.Datastore
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.transaction.TransactionStatus
import gorm.tools.problem.ProblemHandler
import gorm.tools.transaction.TrxService
import grails.persistence.support.PersistenceContextInterceptor
import yakworks.gorm.config.AsyncConfig
/**
* Support service for aysnc to wrap session, transaction, etc...
*
* @author Joshua Burnett (@basejump)
* @since 7.0.9
*/
@Slf4j
@CompileStatic
class AsyncService {
@Autowired AsyncConfig asyncConfig
@Autowired
PersistenceContextInterceptor persistenceInterceptor
@Autowired
TrxService trxService
@Autowired
ProblemHandler problemHandler
// static cheater to get the bean, use sparingly if at all
// static AsyncService getBean(){
// AppCtx.get('asyncService', this)
// }
/**
* run the closure asyncronously,
*
* @param asyncConfig the config object that can have session or transactional set if it should be wrapped
* @param runnable the runnable closure
* @return the CompletableFuture
*/
CompletableFuture runAsync(AsyncArgs asyncArgs, Runnable runnable){
return supplyAsync( asyncArgs, (Supplier)( () -> runnable.run() ))
}
/**
* shortcut that proved a default AsyncConfig
*/
public CompletableFuture supplyAsync(Supplier supplier){
supplyAsync(new AsyncArgs(), supplier)
}
/**
* Supplies a CompletableFuture and returns it, can be wrapped in session or trx if specified in asyncConfig
*
* @param asynArgs the config object that can have session or transactional set if it should be wrapped
* @param closure the runnable closure
* @return the CompletableFuture
*/
public CompletableFuture supplyAsync(AsyncArgs asynArgs, Supplier supplier){
Supplier wrappedSupplier = wrapSupplier(asynArgs, supplier)
if(shouldAsync(asynArgs)){
return CompletableFuture.supplyAsync(wrappedSupplier)
} else {
CompletableFuture syncFuture
//fake it but run it in same thread
try{
T res = wrappedSupplier.get()
syncFuture = CompletableFuture.completedFuture(res)
} catch (AssertionError | Exception e){
syncFuture = new CompletableFuture()
syncFuture.completeExceptionally(e)
}
return syncFuture
}
}
boolean shouldAsync(AsyncArgs asyncArgs){
return asyncArgs.enabled != null ? asyncArgs.enabled : asyncConfig.enabled
}
/**
* if args doesn't have a datastore then it grabs the default one from the trxService
*/
void verifyDatastore(AsyncArgs asyncArgs){
if(!asyncArgs.datastore){
asyncArgs.datastore = trxService.getTargetDatastore()
}
}
/**
* checks args for session or trx and wraps the closure if needed
*
* @param asyncArgs the args that decide if its a trx or session
* @param passItem it true then resulting closure will accept an item arg so it can be used to pass to an each
* @param closure the closure to wrap
* @return the wrapped supplier
*/
public Supplier wrapSupplier(AsyncArgs asyncArgs, Supplier supplier){
Supplier wrappedSupplier
if(asyncArgs.transactional){
verifyDatastore(asyncArgs)
wrappedSupplier = wrapSupplierTrx(asyncArgs.datastore, supplier)
} else if(asyncArgs.session){
verifyDatastore(asyncArgs)
wrappedSupplier = wrapSupplierSession(asyncArgs.datastore, supplier)
} else {
wrappedSupplier = wrapSupplier(supplier)
}
wrappedSupplier
}
/**
* just returns the passed in supplier by default, an be overriden in a super
* which is done for the AsyncSecurityService
*/
public Supplier wrapSupplier(Supplier sup) {
return sup
}
public Supplier wrapSupplierTrx(Datastore ds, Supplier sup) {
return new Supplier() {
@Override
T get() {
trxService.withTrx(ds) { TransactionStatus status ->
return sup.get()
}
}
}
}
@SuppressWarnings(["EmptyCatchBlock"])
public Supplier wrapSupplierSession(Datastore ds, Supplier sup) {
Supplier newsup = () -> {
persistenceInterceptor.init()
try {
return sup.get()
} finally {
try {
//only destroys if new one was created, otherwise does nothing
persistenceInterceptor.destroy()
} catch (Exception e) {
//ignore errors
}
}
}
return newsup
}
public Consumer wrapConsumer(AsyncArgs asyncArgs, Consumer consumer){
Consumer wrappedConsumer
if(asyncArgs.transactional){
verifyDatastore(asyncArgs)
wrappedConsumer = wrapConsumerTrx(asyncArgs.datastore, consumer)
} else if(asyncArgs.session){
verifyDatastore(asyncArgs)
wrappedConsumer = wrapConsumerSession(asyncArgs.datastore, consumer)
} else {
// no wrap so just do closure
wrappedConsumer = wrapConsumer(consumer)
}
wrappedConsumer
}
/**
* wrap the consumer, can be overriden in super which is is done in AsyncSecureService to copy security context into new thread
*/
public Consumer wrapConsumer(Consumer consumer) {
return consumer
}
public Consumer wrapConsumerTrx(Datastore ds, Consumer consumer) {
Consumer newcon = (T item) -> {
trxService.withTrx(ds) { TransactionStatus status ->
consumer.accept(item)
}
}
return newcon
}
@SuppressWarnings(["EmptyCatchBlock"])
public Consumer wrapConsumerSession(Datastore ds, Consumer consumer) {
Consumer newcon = (T item) -> {
persistenceInterceptor.init()
try {
consumer.accept(item)
} finally {
try {
//only destroys if new one was created, otherwise does nothing
persistenceInterceptor.destroy()
} catch (Exception e) {
//ignore errors
}
}
}
return newcon
}
}