package com.yiqiniu.easytrans; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import com.yiqiniu.easytrans.config.EasyTransConifg; import com.yiqiniu.easytrans.context.LogProcessContext; import com.yiqiniu.easytrans.datasource.TransStatusLogger; import com.yiqiniu.easytrans.log.TransactionLogWritter; import com.yiqiniu.easytrans.log.vo.Content; /** * provide base instrument for TCC,MQ transaction support<br> * interact with spring's TransactionSynchronizer */ public class EasyTransSynchronizer { private static final String LOG_PROCESS_CONTEXT = "LOG_PROCESS_CONTEXT"; @Resource private EasyTransConifg config; @Resource private TransactionLogWritter writer; @Resource private ConsistentGuardian consistentGuardian; @Resource private TransStatusLogger transStatusLogger; private ExecutorService executor = Executors.newCachedThreadPool(); private Logger LOG = LoggerFactory.getLogger(this.getClass()); /** * 需要执行的方法 * @param call * @param content * @return */ public <T> Future<T> executeMethod(Callable<T> call,Content content){ LogProcessContext logProcessContext = getLogProcessContext(); return logProcessContext.getExecuteManager().cacheCall(call, content); } /** * create LogContext.<br> * pass in busCode and trxId will help about the execute efficient and the debug and statistics<br> * so I demand to pass in busCode and trxId to generate a global unique bus key * @param busCode the unique busCode in AppId * @param trxId the unique trxId in appId+busCode */ public void startSoftTrans(String busCode,String trxId){ Assert.notNull(busCode); Assert.notNull(trxId); //hook to TransactionSynchronizer TransactionSynchronizationManager.registerSynchronization(new TransactionHook()); LogProcessContext logProcessContext = new LogProcessContext(config.getAppId(), busCode, trxId, writer,transStatusLogger); TransactionSynchronizationManager.bindResource(LOG_PROCESS_CONTEXT,logProcessContext); transStatusLogger.writeExecuteFlag(config.getAppId(), busCode, trxId); } public void registerLog(Content content){ LogProcessContext logProcessContext = getLogProcessContext(); logProcessContext.getLogCache().cacheLog(content); } /** * get log context * @return */ public LogProcessContext getLogProcessContext() { LogProcessContext logProcessContext = (LogProcessContext) TransactionSynchronizationManager.getResource(LOG_PROCESS_CONTEXT); if(logProcessContext == null){ throw new RuntimeException("please call TransController.startSoftTrans() before executeMethods!"); } return logProcessContext; } private void unbindLogProcessContext(){ TransactionSynchronizationManager.unbindResource(LOG_PROCESS_CONTEXT); } private class TransactionHook extends TransactionSynchronizationAdapter{ @Override public void beforeCommit(boolean readOnly) { //flush all the logs and execute all the compensable methods before commit LogProcessContext logProcessContext = getLogProcessContext(); logProcessContext.getLogCache().flush(false); logProcessContext.getExecuteManager().excuteCahcheMehods(); Map<Callable<?>, Exception> errorCalls = logProcessContext.getExecuteManager().getErrorCalls(); if(errorCalls.size() != 0){ Entry<Callable<?>, Exception> next = errorCalls.entrySet().iterator().next(); throw new RuntimeException("Exist compensable method call Exception,rollback now...",next.getValue()); } } @Override public void afterCompletion(int status) { final LogProcessContext logProcessContext = getLogProcessContext(); unbindLogProcessContext(); switch(status){ case STATUS_COMMITTED: logProcessContext.setFinalMasterTransStatus(true); break; case STATUS_ROLLED_BACK: logProcessContext.setFinalMasterTransStatus(false); break; case STATUS_UNKNOWN: logProcessContext.setFinalMasterTransStatus(null); break; default: throw new RuntimeException("Unkonw Status!"); } //unwritten logs are not necessary logs logProcessContext.getLogCache().clearCacheLogs(); //asynchronous execute to enhance efficient executor.execute(new Runnable() { @Override public void run() { try{ consistentGuardian.process(logProcessContext); }catch(Throwable e){ LOG.error("consistentGuardian execute exception!",e); } } }); } } }