package com.aol.micro.server.transactions;
import java.util.function.Consumer;
import java.util.function.Function;
import cyclops.control.Try;
import lombok.AllArgsConstructor;
import lombok.experimental.Wither;
import org.springframework.transaction.support.TransactionTemplate;
/**
* Stream-like monadic transaction processing
*
* @author johnmcclean
*
* @param <T> Data input type for transactional process
* @param <R> Data result type for transactional process
*/
@AllArgsConstructor
@Wither
public class TransactionFlow<T,R> {
private final TransactionTemplate transactionTemplate;
private final Function<T,R> transaction;
/**
* Map from input -> output within the context of a transaction.
* An exception in the mapping process will cause Rollback
* Transaction will be completed once the entire flow has completed
* <pre>
* {@code
* Integer result = TransactionFlow.<Integer>of(transactionTemplate)
.map(this::load)
.map(this::save)
.execute(10)
.get();
* }
* </pre>
*
* @param fn Map from input type to output type within a transaction
* @return TransactionFlow
*/
public <R1> TransactionFlow<T,R1> map(Function<R,R1> fn){
return of(transactionTemplate,transaction.andThen(fn));
}
/**
* Consume current data within the context of a transaction.
* An exception in the consumption process will cause Rollback
* Transaction will be completed once the entire flow has completed
* <pre>
* {code
* Integer result = TransactionFlow.of(transactionTemplate, this::load)
.consume(this::save)
.execute(10)
.get();
* }
* </pre>
*
* @param c Consumer to accept current data
* @return TransactionFlow
*/
public TransactionFlow<T,R> peek(Consumer<R> c){
return map (in-> { c.accept(in); return in;});
}
/**
* Transform data in the context of a Transaction and optionally propgate to / join with a new TransactionalFlow
* * <pre>
* {code
* String result = TransactionFlow.of(transactionTemplate, this::load)
.flatMap(this::newTransaction)
.execute(10)
.get();
* }
* </pre>
*
*
* @param fn flatMap function to be applied
* @return Next stage in the transactional flow
*/
public <R1> TransactionFlow<T,R1> flatMap(Function<R,TransactionFlow<T,R1>> fn){
return of(transactionTemplate,a -> fn.apply(transaction.apply(a)).transaction.apply(a));
}
/**
* Execute the transactional flow - catch all exceptions
*
* @param input Initial data input
* @return Try that represents either success (with result) or failure (with errors)
*/
public Try<R,Throwable> execute(T input){
return Try.withCatch( ()->transactionTemplate.execute(status-> transaction.apply(input)));
}
/**
* Execute the transactional flow - catch only specified exceptions
*
* @param input Initial data input
* @param classes Exception types to catch
* @return Try that represents either success (with result) or failure (with errors)
*/
public <Ex extends Throwable> Try<R,Ex> execute(T input,Class<Ex> classes){
return Try.withCatch( ()->transactionTemplate.execute(status-> transaction.apply(input)),classes);
}
/**
* Create a new Transactional Flow
*
* @param transactionManager Spring TransactionTemplate to manage the transactions
* @param fn Initial function to be applied
* @return Create a new Transactional Flow
*/
public static <T,R> TransactionFlow<T,R> of(TransactionTemplate transactionManager,Function<T,R> fn){
return new TransactionFlow<>(transactionManager,fn);
}
/**
* Create a new Transactional Flow
*
* @param transactionManager Spring TransactionTemplate to manage the transactions
* @return Create a new Transactional Flow
*/
public static <T> TransactionFlow<T,T> of(TransactionTemplate transactionManager){
return new TransactionFlow<>(transactionManager,Function.identity());
}
}