package com.yiqiniu.easytrans;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.springframework.util.Assert;
import com.yiqiniu.easytrans.context.LogProcessContext;
import com.yiqiniu.easytrans.log.vo.Content;
/**
* batch write logs and batch execute Compensable methods to enhance efficient
*/
public class ExecuteCacheManager {
//Set a max thread count, if accumulate to fast will reject execute for efficient
private static ExecutorService executor = new ThreadPoolExecutor(0, 2048, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
private ConcurrentHashMap<Callable<?>,Object[]/*0 Result,1 Exception,2 Boolean(Executed?)*/> mapCallable = new ConcurrentHashMap<Callable<?>,Object[]/*1 Result,2 Exception*/>();
private LogProcessContext logCtx;
private Map<Callable<?>,Exception> mapErrorCalls = new HashMap<Callable<?>, Exception>();
public Map<Callable<?>,Exception> getErrorCalls(){
return new HashMap<Callable<?>, Exception>(mapErrorCalls);//copy to protect orign data
}
public ExecuteCacheManager(LogProcessContext logCtx){
this.logCtx = logCtx;
}
public <T> Future<T> cacheCall(Callable<T> compensableCall,Content content){
logCtx.getLogCache().cacheLogList(Arrays.asList(content));
mapCallable.put(compensableCall,new Object[3]);
CompensableCallerWrapper<T> compensableCallWrapper = new CompensableCallerWrapper<T>(compensableCall);
Future<T> future = new ExecuteTriggerByGetFuture<T>(compensableCallWrapper);
return future;
}
/**
* Use Combination Pattern to implement.
*
* the callable method will be trigger by calling get() method
*/
private class ExecuteTriggerByGetFuture<T> implements Future<T>{
private FutureTask<T> futureTask;
public ExecuteTriggerByGetFuture(Callable<T> callable) {
super();
this.futureTask = new FutureTask<T>(callable);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// implements later
throw new RuntimeException("Not support operation");
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return futureTask.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
futureTask.run();
return futureTask.get();
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
futureTask.run();
return futureTask.get(timeout, unit);
}
}
/**
* Wrap the compensableCall for efficient
*
* @param <T>
*/
private class CompensableCallerWrapper<T> implements Callable<T>{
private Callable<T> compensableCall;
private long objCreateThread;
public CompensableCallerWrapper(Callable<T> compensableCall) {
super();
this.compensableCall = compensableCall;
objCreateThread = Thread.currentThread().getId();
}
@SuppressWarnings("unchecked")
@Override
public T call() throws Exception {
Assert.isTrue(objCreateThread == Thread.currentThread().getId(),"It's not thread safe,do not run in other threads");
//before compensable methods call,flush all the logs
logCtx.getLogCache().flush(false);
//batch execute all the cached compensable methods
excuteCahcheMehods();
//get the specific result of compensableCall
Object[] objects = mapCallable.get(compensableCall);
if(objects[2] == null || objects[2].equals(false)){
throw new RuntimeException("Unkonw Error,It should has value!");
}
if(objects[1] != null){
throw (Exception)objects[1];
}else{
return (T)objects[0];
}
}
}
public void excuteCahcheMehods() {
Map<Callable<?>,Future<?>> listFuture = new HashMap<Callable<?>,Future<?>>();
for(Entry<Callable<?>, Object[]> entry :mapCallable.entrySet()){
Object[] results = entry.getValue();
if(results[2] == null || results[2].equals(false)){
//did not execute,then execute
Callable<?> caller = entry.getKey();
listFuture.put(caller, executor.submit(caller));
}
}
//get and place the results in map
for(Entry<Callable<?>, Future<?>> entry:listFuture.entrySet()){
Callable<?> caller = entry.getKey();
Future<?> future = entry.getValue();
try {
mapCallable.put(caller, new Object[]{future.get(),null,true});
} catch (InterruptedException e) {
mapErrorCalls.put(caller, e);
throw new RuntimeException(e);
} catch (ExecutionException e) {
mapErrorCalls.put(caller, e);
mapCallable.put(caller, new Object[]{null,e.getCause(),true});
}
}
}
}