├── .gitignore ├── README.md ├── pom.xml ├── tcctx-console ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── jd │ │ └── tx │ │ └── tcc │ │ ├── console │ │ └── TXMonitorServlet.java │ │ └── service │ │ └── TransactionQueryService.java │ └── resources │ └── console │ └── http │ ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap-theme.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ └── style.css │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── index.html │ └── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── doT.js │ ├── jquery-2.2.3.min.js │ ├── jquery.min.js │ ├── lang.js │ └── npm.js ├── tcctx-core ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── jd │ │ └── tx │ │ └── tcc │ │ └── core │ │ ├── ResourceItem.java │ │ ├── ResourceItemLinkedList.java │ │ ├── StateGenerator.java │ │ ├── TransactionContext.java │ │ ├── TransactionManager.java │ │ ├── TransactionResource.java │ │ ├── TransactionRunner.java │ │ ├── entity │ │ └── TransactionEntity.java │ │ ├── exception │ │ ├── SOATxUnawareException.java │ │ └── SOATxUnrecoverableException.java │ │ ├── impl │ │ ├── CommonTransactionContext.java │ │ ├── JDBCHelper.java │ │ └── SeqStateGenerator.java │ │ ├── query │ │ └── TransactionQuery.java │ │ ├── retry │ │ └── RetryJob.java │ │ ├── state │ │ ├── BeginState.java │ │ ├── CancelState.java │ │ ├── ConfirmState.java │ │ ├── StateContext.java │ │ ├── TransactionAction.java │ │ ├── TransactionState.java │ │ └── TryState.java │ │ ├── sync │ │ └── SyncTransactionRunner.java │ │ └── utils │ │ └── Utils.java │ └── test │ ├── java │ └── com │ │ └── jd │ │ └── tx │ │ └── tcc │ │ └── core │ │ ├── TestMain.java │ │ ├── TestResourceItemLinkedList.java │ │ ├── TestTransactionRunner.java │ │ ├── impl │ │ ├── TestJDBCHelper.java │ │ └── TestSeqStateGenerator.java │ │ └── test │ │ ├── hsql │ │ ├── HsqlDatabase.java │ │ └── TestTableManager.java │ │ └── mock │ │ └── MockResourceItem.java │ └── resources │ └── logback-test.xml └── tcctx-job ├── pom.xml └── src ├── main └── java │ └── com │ └── jd │ └── tx │ └── tcc │ └── job │ └── SyncJobRetryScheduler.java └── test └── java └── com └── jd └── tx └── tcc └── job └── TestSyncJobRetryScheduler.java /.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | target/ 3 | *.jar 4 | *.war 5 | *.zip 6 | *.tar 7 | *.tar.gz 8 | 9 | # eclipse ignore 10 | .settings/ 11 | .project 12 | .classpath 13 | 14 | # idea ignore 15 | .idea/ 16 | *.ipr 17 | *.iml 18 | *.iws 19 | 20 | # temp ignore 21 | logs/ 22 | *.doc 23 | *.log 24 | *.cache 25 | *.diff 26 | *.patch 27 | *.tmp 28 | 29 | # system ignore 30 | .DS_Store 31 | Thumbs.db 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JD-TccTx 2 | 分布式事务管理框架,实现基于Try Confirm Cancel的分布式事务调度。 3 | 目前提供了同步事务的自动调度能力,并能进行回滚和失败任务的异步重试流程。 4 | 5 | ## How to build 6 | 7 | 编译Druid Notice需要如下资源: 8 | 9 | * Latest stable [Oracle JDK 7](http://www.oracle.com/technetwork/java/) 10 | * Latest stable [Apache Maven](http://maven.apache.org/) 11 | 12 | 下载代码及编译方法 13 | ``` 14 | git clone git@git.jd.com:pop-commons/jd-tcctx.git 15 | cd jd-tcctx 16 | mvn -DskipTests=true package 17 | ``` 18 | 19 | ## Features 20 | 21 | * 基于业务系统自身的事务主表进行事务驱动(需配置额外的状态和处理时间字段) 22 | * 子事务资源只需提供try, confirm, cancel三个方法(try和cancel可被裁减),就能自动进行事务驱动 23 | * 通过异常自动控制事务流程进入异步重试还是回滚流程 24 | * 提供积压任务监控页面,方便查看当前任务状态 25 | 26 | ## Development guide 27 | 28 | #### 1. 增加Maven依赖 29 | 30 | 在项目pom.xml增加依赖 31 | ```xml 32 | 33 | ... 34 | 35 | ... 36 | 37 | 38 | com.jd.tx 39 | tcctx-core 40 | 1.0-SNAPSHOT 41 | 42 | 43 | 44 | 45 | com.jd.tx 46 | tcctx-console 47 | 1.0-SNAPSHOT 48 | 49 | ... 50 | 51 | ... 52 | 53 | ``` 54 | 55 | #### 2. 包装实现业务方法 56 | 57 | 将事务流程分离为若干能**独立保持原子性、强一致性和幂等性**的子事务单元。 58 | 每个子事务单元实现接口[ResourceItem](http://git.jd.com/pop-commons/jd-tcctx/blob/master/tcctx-core/src/main/java/com/jd/tx/tcc/core/ResourceItem.java)。 59 | 其中可以裁减的是`tryTx()`和`cancelTx()`方法,需要单独在`hasTry()`或`hasCancel()`中`return false;`。 60 | 61 | ------------------------------------------------------------------------------- 62 | 63 | 需要注意的是在try, confirm, cancel的业务方法中,如果出现异常,可以选择两种抛出方式: 64 | * [SOATxUnawareException](http://git.jd.com/pop-commons/jd-tcctx/blob/master/tcctx-core/src/main/java/com/jd/tx/tcc/core/exception/SOATxUnawareException.java) 65 | * [SOATxUnrecoverableException](http://git.jd.com/pop-commons/jd-tcctx/blob/master/tcctx-core/src/main/java/com/jd/tx/tcc/core/exception/SOATxUnrecoverableException.java) 66 | 67 | > 当抛出`SOATxUnawareException`或其他继承自`Throwable`的异常后,事务流程认为发生了不可预期的异常(例如网络超时等),当前事务线程会中断并直接抛出该异常,但是保留当前事务,后续会有异步任务自动根据当前状态进行重试。 68 | > 而当抛出`SOATxUnrecoverableException`后,事务流程认为发生了预期进行回滚的异常(例如资源预占失败,数据库主键重复等可预期的异常),事务流程会依次调用当前节点直至最初节点的`cancelTx()`方法,直到全部回滚成功并抛出该异常。 69 | 70 | **也就是说tcctx是以抛出异常的类型进行事务流程驱动的** 71 | 72 | #### 3. 在spring中配置TCC事务 73 | 74 | ##### 3.1 配置事务主表和子事务相关 75 | 76 | 样例: 77 | ```xml 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ``` 105 | 106 | ##### 3.2 配置事务资源管理器 107 | 108 | 样例: 109 | ```xml 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | ``` 120 | 121 | ##### 3.3 配置提交执行器 122 | 123 | 目前默认只提供了一种实现: 124 | ```xml 125 | 126 | ``` 127 | 128 | #### 4. 在代码中提交TCC事务 129 | 130 | 首先获得spring context中的`TransactionRunner` 131 | ```java 132 | @Autowired 133 | private TransactionRunner transactionRunner; 134 | ``` 135 | 构建TransactionContext,需要传入事务主表所需的`DataSource`和本次事务主对象txObject 136 | ```java 137 | TransactionContext context = TXContextFactory.buildNew(dataSource, txObject); 138 | ... 139 | //此处的key与事务资源管理器中的key相对应 140 | public static final String CONTEXT_KEY = "points2Coupon"; 141 | 142 | public static CommonTransactionContext buildNew( 143 | DataSource dataSource, 144 | CustomerPointsFreeze pointsFreeze) { 145 | CommonTransactionContext context = new CommonTransactionContext(); 146 | context.setKey(CONTEXT_KEY); 147 | context.setDataSource(dataSource); 148 | context.setId(pointsFreeze.getBusinessId()); 149 | context.setResourceObject(pointsFreeze); 150 | //新提交的状态都为begin 151 | context.setState(transactionResource.getBeginningState()); 152 | return context; 153 | } 154 | ``` 155 | 提交事务,如果抛出`SOATxUnawareException`表示事务进入异步重试流程。如果抛出`SOATxUnrecoverableException`表示事务失败,没有提交成功或者全部回滚完成。 156 | ```java 157 | transactionRunner.run(context); 158 | ``` 159 | #### 5. 异步重试任务的配置 160 | 161 | ##### 5.1 使用Executor Scheduler进行异步任务的驱动 162 | 163 | 初始化`com.jd.tx.tcc.core.retry.RetryJob`,并调用`start()`方法,会根据配置自动进行超时任务的重试。此种方式较为轻量级,但是不具备分片,HA,Failover能力。 164 | 165 | ##### 5.2 使用elastic-job进行异步任务的驱动 166 | 167 | 这种任务基于elastic-job进行调度,相对重量级,但是通过elastic-job实现了分片,Failover能力。但是需要单独部署一套elastic-job。 168 | 引入tcctx-job依赖: 169 | ```xml 170 | 171 | ... 172 | 173 | ... 174 | 175 | 176 | com.jd.tx 177 | tcctx-job 178 | 1.0-SNAPSHOT 179 | 180 | ... 181 | 182 | ... 183 | 184 | ``` 185 | 样例: 186 | ```xml 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | ``` 212 | 213 | #### 6. 积压中任务的监控 214 | 215 | 在J2EE容器的web.xml中配置servlet: 216 | ```xml 217 | 218 | 219 | TccTxEntityView 220 | com.jd.tx.tcc.console.TXMonitorServlet 221 | 222 | queryBeanName 223 | transactionQueryService 224 | 225 | 226 | 227 | TccTxEntityView 228 | /tcctx/* 229 | 230 | ``` 231 | 启动web容器后,访问`http://yoursite/tccctx/index.html`进行监控 232 | 233 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.jd.tx 8 | tcc 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | tcctx-core 13 | tcctx-job 14 | tcctx-console 15 | 16 | 17 | 18 | 1.7 19 | 3.3 20 | 2.7 21 | 2.6 22 | 3.0.0 23 | 24 | 4.12 25 | 3.4.2 26 | 1.10.19 27 | 4.1.1.RELEASE 28 | 1.7.7 29 | 1.1.2 30 | 31 | 32 | 33 | 34 | org.apache.commons 35 | commons-lang3 36 | 3.3.2 37 | 38 | 39 | org.apache.commons 40 | commons-collections4 41 | 4.0 42 | 43 | 44 | org.projectlombok 45 | lombok 46 | 1.16.6 47 | 48 | 49 | com.google.guava 50 | guava 51 | 14.0 52 | 53 | 54 | 55 | junit 56 | junit 57 | ${junit.version} 58 | test 59 | 60 | 61 | org.unitils 62 | unitils-core 63 | ${unitils.core.version} 64 | test 65 | 66 | 67 | org.mockito 68 | mockito-core 69 | ${mockito.version} 70 | test 71 | 72 | 73 | org.hamcrest 74 | hamcrest-core 75 | 76 | 77 | 78 | 79 | org.springframework 80 | spring-test 81 | ${springframework.version} 82 | test 83 | 84 | 85 | 86 | 87 | org.slf4j 88 | slf4j-api 89 | ${slf4j.version} 90 | provided 91 | true 92 | 93 | 94 | org.slf4j 95 | jcl-over-slf4j 96 | ${slf4j.version} 97 | runtime 98 | 99 | 100 | org.slf4j 101 | log4j-over-slf4j 102 | ${slf4j.version} 103 | runtime 104 | 105 | 106 | ch.qos.logback 107 | logback-classic 108 | ${logback.version} 109 | provided 110 | true 111 | 112 | 113 | org.slf4j 114 | slf4j-api 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-compiler-plugin 128 | 129 | ${java.version} 130 | ${java.version} 131 | ${java.version} 132 | ${java.version} 133 | 134 | ${maven-compiler-plugin.version} 135 | 136 | 137 | org.apache.maven.plugins 138 | maven-resources-plugin 139 | ${maven-resources-plugin.version} 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-jar-plugin 144 | ${maven-jar-plugin.version} 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /tcctx-console/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | tcc 7 | com.jd.tx 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | tcctx-console 13 | 14 | 15 | 16 | com.jd.tx 17 | tcctx-core 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | javax.servlet 23 | javax.servlet-api 24 | 3.1.0 25 | provided 26 | true 27 | 28 | 29 | 30 | com.alibaba 31 | fastjson 32 | 1.1.45 33 | provided 34 | true 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tcctx-console/src/main/java/com/jd/tx/tcc/console/TXMonitorServlet.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.console; 2 | 3 | import com.jd.tx.tcc.core.utils.Utils; 4 | import com.jd.tx.tcc.service.TransactionQueryService; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.apache.commons.lang3.Validate; 7 | import org.springframework.web.context.WebApplicationContext; 8 | import org.springframework.web.context.support.WebApplicationContextUtils; 9 | 10 | import javax.servlet.ServletContext; 11 | import javax.servlet.ServletException; 12 | import javax.servlet.http.HttpServlet; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | /** 18 | * @author Leon Guo 19 | * Creation Date: 2016/4/8 20 | */ 21 | public class TXMonitorServlet extends HttpServlet { 22 | 23 | protected final String resourcePath; 24 | 25 | protected final static String REST_PREFIX = "/rest/"; 26 | 27 | protected final static String PARAM_QUERY_BEAN_NAME = "queryBeanName"; 28 | 29 | private String queryBeanName; 30 | 31 | public TXMonitorServlet(){ 32 | this.resourcePath = "console/http"; 33 | } 34 | 35 | @Override 36 | public void init() throws ServletException { 37 | queryBeanName = getInitParameter(PARAM_QUERY_BEAN_NAME); 38 | Validate.notEmpty(queryBeanName); 39 | } 40 | 41 | @Override 42 | public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 43 | String contextPath = request.getContextPath(); 44 | String servletPath = request.getServletPath(); 45 | String requestURI = request.getRequestURI(); 46 | 47 | response.setCharacterEncoding("utf-8"); 48 | 49 | if (contextPath == null) { // root context 50 | contextPath = ""; 51 | } 52 | String uri = contextPath + servletPath; 53 | String path = requestURI.substring(contextPath.length() + servletPath.length()); 54 | 55 | if (path.startsWith(REST_PREFIX) && path.length() > REST_PREFIX.length()) { 56 | String resource = path.substring(REST_PREFIX.length()); 57 | TransactionQueryService queryService = getQueryService(request); 58 | if ("dataSourceKey".equals(resource)) { 59 | response.getWriter().write(queryService.queryDataSourceKeys()); 60 | return; 61 | } else if ("transactionEntity".equals(resource)) { 62 | response.getWriter().write(queryService.queryTransactionEntities( 63 | request.getParameter("dataSourceKey"), request.getParameter("lastId"))); 64 | return; 65 | } 66 | } 67 | if (path.startsWith("/index.html") || StringUtils.isBlank(path) || "/".equals(path)) { 68 | returnResourceFile("/index.html", uri, response); 69 | return; 70 | } 71 | returnResourceFile(path, uri, response); 72 | } 73 | 74 | protected String getFilePath(String fileName) { 75 | return resourcePath + fileName; 76 | } 77 | 78 | protected void returnResourceFile(String fileName, String uri, HttpServletResponse response) 79 | throws ServletException, 80 | IOException { 81 | 82 | String filePath = getFilePath(fileName); 83 | 84 | if (filePath.endsWith(".html")) { 85 | response.setContentType("text/html; charset=utf-8"); 86 | } 87 | if (fileName.endsWith(".jpg")) { 88 | byte[] bytes = Utils.readByteArrayFromResource(filePath); 89 | if (bytes != null) { 90 | response.getOutputStream().write(bytes); 91 | } 92 | 93 | return; 94 | } 95 | 96 | String text = Utils.readFromResource(filePath); 97 | if (text == null) { 98 | response.sendRedirect(uri + "/index.html"); 99 | return; 100 | } 101 | if (fileName.endsWith(".css")) { 102 | response.setContentType("text/css;charset=utf-8"); 103 | } else if (fileName.endsWith(".js")) { 104 | response.setContentType("text/javascript;charset=utf-8"); 105 | } 106 | response.getWriter().write(text); 107 | } 108 | 109 | 110 | protected TransactionQueryService getQueryService(HttpServletRequest request) { 111 | return (TransactionQueryService) getSpringContext(request).getBean(queryBeanName); 112 | } 113 | 114 | protected WebApplicationContext getSpringContext(HttpServletRequest request) { 115 | ServletContext context = request.getSession().getServletContext(); 116 | WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(context); 117 | return wac; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /tcctx-console/src/main/java/com/jd/tx/tcc/service/TransactionQueryService.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.jd.tx.tcc.core.TransactionResource; 5 | import com.jd.tx.tcc.core.entity.TransactionEntity; 6 | import com.jd.tx.tcc.core.impl.CommonTransactionContext; 7 | import com.jd.tx.tcc.core.query.TransactionQuery; 8 | import lombok.NonNull; 9 | import lombok.Setter; 10 | import org.apache.commons.lang3.Validate; 11 | 12 | import javax.sql.DataSource; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author Leon Guo 18 | * Creation Date: 2016/4/11 19 | */ 20 | public class TransactionQueryService { 21 | 22 | @Setter 23 | private Map dataSourceMap; 24 | 25 | @Setter 26 | private TransactionResource txResource; 27 | 28 | @Setter 29 | private int timeoutMinutes = 2; 30 | 31 | public String queryTransactionEntities(@NonNull String dataSourceKey, String lastId) { 32 | Validate.notNull(dataSourceMap); 33 | Validate.notNull(txResource); 34 | 35 | CommonTransactionContext txContext = new CommonTransactionContext(); 36 | txContext.setDataSource(dataSourceMap.get(dataSourceKey)); 37 | List transactionEntities = 38 | new TransactionQuery(txContext, txResource) 39 | .setQueryRows(50) 40 | .setLastId(lastId) 41 | .setMinutesBefore(timeoutMinutes) 42 | .query(); 43 | 44 | return JSON.toJSONStringWithDateFormat(transactionEntities, "yyyyMMdd HH:mm:ss"); 45 | } 46 | 47 | public String queryDataSourceKeys() { 48 | Validate.notNull(dataSourceMap); 49 | return JSON.toJSONString(dataSourceMap.keySet()); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | .btn-default, 7 | .btn-primary, 8 | .btn-success, 9 | .btn-info, 10 | .btn-warning, 11 | .btn-danger { 12 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 13 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 14 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | } 16 | .btn-default:active, 17 | .btn-primary:active, 18 | .btn-success:active, 19 | .btn-info:active, 20 | .btn-warning:active, 21 | .btn-danger:active, 22 | .btn-default.active, 23 | .btn-primary.active, 24 | .btn-success.active, 25 | .btn-info.active, 26 | .btn-warning.active, 27 | .btn-danger.active { 28 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 29 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | } 31 | .btn-default.disabled, 32 | .btn-primary.disabled, 33 | .btn-success.disabled, 34 | .btn-info.disabled, 35 | .btn-warning.disabled, 36 | .btn-danger.disabled, 37 | .btn-default[disabled], 38 | .btn-primary[disabled], 39 | .btn-success[disabled], 40 | .btn-info[disabled], 41 | .btn-warning[disabled], 42 | .btn-danger[disabled], 43 | fieldset[disabled] .btn-default, 44 | fieldset[disabled] .btn-primary, 45 | fieldset[disabled] .btn-success, 46 | fieldset[disabled] .btn-info, 47 | fieldset[disabled] .btn-warning, 48 | fieldset[disabled] .btn-danger { 49 | -webkit-box-shadow: none; 50 | box-shadow: none; 51 | } 52 | .btn-default .badge, 53 | .btn-primary .badge, 54 | .btn-success .badge, 55 | .btn-info .badge, 56 | .btn-warning .badge, 57 | .btn-danger .badge { 58 | text-shadow: none; 59 | } 60 | .btn:active, 61 | .btn.active { 62 | background-image: none; 63 | } 64 | .btn-default { 65 | text-shadow: 0 1px 0 #fff; 66 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 67 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 68 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 69 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 71 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 72 | background-repeat: repeat-x; 73 | border-color: #dbdbdb; 74 | border-color: #ccc; 75 | } 76 | .btn-default:hover, 77 | .btn-default:focus { 78 | background-color: #e0e0e0; 79 | background-position: 0 -15px; 80 | } 81 | .btn-default:active, 82 | .btn-default.active { 83 | background-color: #e0e0e0; 84 | border-color: #dbdbdb; 85 | } 86 | .btn-default.disabled, 87 | .btn-default[disabled], 88 | fieldset[disabled] .btn-default, 89 | .btn-default.disabled:hover, 90 | .btn-default[disabled]:hover, 91 | fieldset[disabled] .btn-default:hover, 92 | .btn-default.disabled:focus, 93 | .btn-default[disabled]:focus, 94 | fieldset[disabled] .btn-default:focus, 95 | .btn-default.disabled.focus, 96 | .btn-default[disabled].focus, 97 | fieldset[disabled] .btn-default.focus, 98 | .btn-default.disabled:active, 99 | .btn-default[disabled]:active, 100 | fieldset[disabled] .btn-default:active, 101 | .btn-default.disabled.active, 102 | .btn-default[disabled].active, 103 | fieldset[disabled] .btn-default.active { 104 | background-color: #e0e0e0; 105 | background-image: none; 106 | } 107 | .btn-primary { 108 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 109 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 111 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 113 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 114 | background-repeat: repeat-x; 115 | border-color: #245580; 116 | } 117 | .btn-primary:hover, 118 | .btn-primary:focus { 119 | background-color: #265a88; 120 | background-position: 0 -15px; 121 | } 122 | .btn-primary:active, 123 | .btn-primary.active { 124 | background-color: #265a88; 125 | border-color: #245580; 126 | } 127 | .btn-primary.disabled, 128 | .btn-primary[disabled], 129 | fieldset[disabled] .btn-primary, 130 | .btn-primary.disabled:hover, 131 | .btn-primary[disabled]:hover, 132 | fieldset[disabled] .btn-primary:hover, 133 | .btn-primary.disabled:focus, 134 | .btn-primary[disabled]:focus, 135 | fieldset[disabled] .btn-primary:focus, 136 | .btn-primary.disabled.focus, 137 | .btn-primary[disabled].focus, 138 | fieldset[disabled] .btn-primary.focus, 139 | .btn-primary.disabled:active, 140 | .btn-primary[disabled]:active, 141 | fieldset[disabled] .btn-primary:active, 142 | .btn-primary.disabled.active, 143 | .btn-primary[disabled].active, 144 | fieldset[disabled] .btn-primary.active { 145 | background-color: #265a88; 146 | background-image: none; 147 | } 148 | .btn-success { 149 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 150 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 151 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 152 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 154 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 155 | background-repeat: repeat-x; 156 | border-color: #3e8f3e; 157 | } 158 | .btn-success:hover, 159 | .btn-success:focus { 160 | background-color: #419641; 161 | background-position: 0 -15px; 162 | } 163 | .btn-success:active, 164 | .btn-success.active { 165 | background-color: #419641; 166 | border-color: #3e8f3e; 167 | } 168 | .btn-success.disabled, 169 | .btn-success[disabled], 170 | fieldset[disabled] .btn-success, 171 | .btn-success.disabled:hover, 172 | .btn-success[disabled]:hover, 173 | fieldset[disabled] .btn-success:hover, 174 | .btn-success.disabled:focus, 175 | .btn-success[disabled]:focus, 176 | fieldset[disabled] .btn-success:focus, 177 | .btn-success.disabled.focus, 178 | .btn-success[disabled].focus, 179 | fieldset[disabled] .btn-success.focus, 180 | .btn-success.disabled:active, 181 | .btn-success[disabled]:active, 182 | fieldset[disabled] .btn-success:active, 183 | .btn-success.disabled.active, 184 | .btn-success[disabled].active, 185 | fieldset[disabled] .btn-success.active { 186 | background-color: #419641; 187 | background-image: none; 188 | } 189 | .btn-info { 190 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 191 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 193 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 196 | background-repeat: repeat-x; 197 | border-color: #28a4c9; 198 | } 199 | .btn-info:hover, 200 | .btn-info:focus { 201 | background-color: #2aabd2; 202 | background-position: 0 -15px; 203 | } 204 | .btn-info:active, 205 | .btn-info.active { 206 | background-color: #2aabd2; 207 | border-color: #28a4c9; 208 | } 209 | .btn-info.disabled, 210 | .btn-info[disabled], 211 | fieldset[disabled] .btn-info, 212 | .btn-info.disabled:hover, 213 | .btn-info[disabled]:hover, 214 | fieldset[disabled] .btn-info:hover, 215 | .btn-info.disabled:focus, 216 | .btn-info[disabled]:focus, 217 | fieldset[disabled] .btn-info:focus, 218 | .btn-info.disabled.focus, 219 | .btn-info[disabled].focus, 220 | fieldset[disabled] .btn-info.focus, 221 | .btn-info.disabled:active, 222 | .btn-info[disabled]:active, 223 | fieldset[disabled] .btn-info:active, 224 | .btn-info.disabled.active, 225 | .btn-info[disabled].active, 226 | fieldset[disabled] .btn-info.active { 227 | background-color: #2aabd2; 228 | background-image: none; 229 | } 230 | .btn-warning { 231 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 232 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 233 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 234 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 236 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 237 | background-repeat: repeat-x; 238 | border-color: #e38d13; 239 | } 240 | .btn-warning:hover, 241 | .btn-warning:focus { 242 | background-color: #eb9316; 243 | background-position: 0 -15px; 244 | } 245 | .btn-warning:active, 246 | .btn-warning.active { 247 | background-color: #eb9316; 248 | border-color: #e38d13; 249 | } 250 | .btn-warning.disabled, 251 | .btn-warning[disabled], 252 | fieldset[disabled] .btn-warning, 253 | .btn-warning.disabled:hover, 254 | .btn-warning[disabled]:hover, 255 | fieldset[disabled] .btn-warning:hover, 256 | .btn-warning.disabled:focus, 257 | .btn-warning[disabled]:focus, 258 | fieldset[disabled] .btn-warning:focus, 259 | .btn-warning.disabled.focus, 260 | .btn-warning[disabled].focus, 261 | fieldset[disabled] .btn-warning.focus, 262 | .btn-warning.disabled:active, 263 | .btn-warning[disabled]:active, 264 | fieldset[disabled] .btn-warning:active, 265 | .btn-warning.disabled.active, 266 | .btn-warning[disabled].active, 267 | fieldset[disabled] .btn-warning.active { 268 | background-color: #eb9316; 269 | background-image: none; 270 | } 271 | .btn-danger { 272 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 273 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 274 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 275 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 277 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 278 | background-repeat: repeat-x; 279 | border-color: #b92c28; 280 | } 281 | .btn-danger:hover, 282 | .btn-danger:focus { 283 | background-color: #c12e2a; 284 | background-position: 0 -15px; 285 | } 286 | .btn-danger:active, 287 | .btn-danger.active { 288 | background-color: #c12e2a; 289 | border-color: #b92c28; 290 | } 291 | .btn-danger.disabled, 292 | .btn-danger[disabled], 293 | fieldset[disabled] .btn-danger, 294 | .btn-danger.disabled:hover, 295 | .btn-danger[disabled]:hover, 296 | fieldset[disabled] .btn-danger:hover, 297 | .btn-danger.disabled:focus, 298 | .btn-danger[disabled]:focus, 299 | fieldset[disabled] .btn-danger:focus, 300 | .btn-danger.disabled.focus, 301 | .btn-danger[disabled].focus, 302 | fieldset[disabled] .btn-danger.focus, 303 | .btn-danger.disabled:active, 304 | .btn-danger[disabled]:active, 305 | fieldset[disabled] .btn-danger:active, 306 | .btn-danger.disabled.active, 307 | .btn-danger[disabled].active, 308 | fieldset[disabled] .btn-danger.active { 309 | background-color: #c12e2a; 310 | background-image: none; 311 | } 312 | .thumbnail, 313 | .img-thumbnail { 314 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 315 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 316 | } 317 | .dropdown-menu > li > a:hover, 318 | .dropdown-menu > li > a:focus { 319 | background-color: #e8e8e8; 320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 325 | background-repeat: repeat-x; 326 | } 327 | .dropdown-menu > .active > a, 328 | .dropdown-menu > .active > a:hover, 329 | .dropdown-menu > .active > a:focus { 330 | background-color: #2e6da4; 331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .navbar-default { 339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 345 | background-repeat: repeat-x; 346 | border-radius: 4px; 347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 349 | } 350 | .navbar-default .navbar-nav > .open > a, 351 | .navbar-default .navbar-nav > .active > a { 352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 357 | background-repeat: repeat-x; 358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 360 | } 361 | .navbar-brand, 362 | .navbar-nav > li > a { 363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 364 | } 365 | .navbar-inverse { 366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 372 | background-repeat: repeat-x; 373 | border-radius: 4px; 374 | } 375 | .navbar-inverse .navbar-nav > .open > a, 376 | .navbar-inverse .navbar-nav > .active > a { 377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 382 | background-repeat: repeat-x; 383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 385 | } 386 | .navbar-inverse .navbar-brand, 387 | .navbar-inverse .navbar-nav > li > a { 388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 389 | } 390 | .navbar-static-top, 391 | .navbar-fixed-top, 392 | .navbar-fixed-bottom { 393 | border-radius: 0; 394 | } 395 | @media (max-width: 767px) { 396 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 399 | color: #fff; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 405 | background-repeat: repeat-x; 406 | } 407 | } 408 | .alert { 409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 412 | } 413 | .alert-success { 414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 419 | background-repeat: repeat-x; 420 | border-color: #b2dba1; 421 | } 422 | .alert-info { 423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 428 | background-repeat: repeat-x; 429 | border-color: #9acfea; 430 | } 431 | .alert-warning { 432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 437 | background-repeat: repeat-x; 438 | border-color: #f5e79e; 439 | } 440 | .alert-danger { 441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 446 | background-repeat: repeat-x; 447 | border-color: #dca7a7; 448 | } 449 | .progress { 450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .progress-bar { 458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .progress-bar-success { 466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 471 | background-repeat: repeat-x; 472 | } 473 | .progress-bar-info { 474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 479 | background-repeat: repeat-x; 480 | } 481 | .progress-bar-warning { 482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 487 | background-repeat: repeat-x; 488 | } 489 | .progress-bar-danger { 490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 495 | background-repeat: repeat-x; 496 | } 497 | .progress-bar-striped { 498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 501 | } 502 | .list-group { 503 | border-radius: 4px; 504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 506 | } 507 | .list-group-item.active, 508 | .list-group-item.active:hover, 509 | .list-group-item.active:focus { 510 | text-shadow: 0 -1px 0 #286090; 511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 516 | background-repeat: repeat-x; 517 | border-color: #2b669a; 518 | } 519 | .list-group-item.active .badge, 520 | .list-group-item.active:hover .badge, 521 | .list-group-item.active:focus .badge { 522 | text-shadow: none; 523 | } 524 | .panel { 525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 527 | } 528 | .panel-default > .panel-heading { 529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 534 | background-repeat: repeat-x; 535 | } 536 | .panel-primary > .panel-heading { 537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 542 | background-repeat: repeat-x; 543 | } 544 | .panel-success > .panel-heading { 545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 550 | background-repeat: repeat-x; 551 | } 552 | .panel-info > .panel-heading { 553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 558 | background-repeat: repeat-x; 559 | } 560 | .panel-warning > .panel-heading { 561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 566 | background-repeat: repeat-x; 567 | } 568 | .panel-danger > .panel-heading { 569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 574 | background-repeat: repeat-x; 575 | } 576 | .well { 577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 582 | background-repeat: repeat-x; 583 | border-color: #dcdcdc; 584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 586 | } 587 | /*# sourceMappingURL=bootstrap-theme.css.map */ 588 | -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"} -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | body{ 3 | font-size:12px; 4 | padding-top: 60px; 5 | padding-bottom: 40px; 6 | } 7 | a { 8 | cursor:pointer; 9 | } 10 | #dataTable th > a { 11 | color: #555555; 12 | } 13 | #dataTable th { 14 | background-color: #E5E5E5; 15 | border-bottom-color: #FFC40D; 16 | color: #555555; 17 | cursor: pointer; 18 | } 19 | 20 | .footer { 21 | background-color: #F5F5F5; 22 | border-top: 1px solid #E5E5E5; 23 | margin-top: 10px; 24 | padding: 20px 0; 25 | } 26 | 27 | .td_lable { 28 | font-weight: bold; 29 | width: 220px; 30 | background-color: #E5E5E5; 31 | } 32 | 33 | 34 | .striped { 35 | background-color: #fff; 36 | } 37 | 38 | #dataTable1 th > a { 39 | color: #555555; 40 | } 41 | #dataTable1 th { 42 | background-color: #E5E5E5; 43 | border-bottom-color: #FFC40D; 44 | color: #555555; 45 | cursor: pointer; 46 | } 47 | 48 | 49 | #dataTable2 th > a { 50 | color: #555555; 51 | } 52 | #dataTable2 th { 53 | background-color: #E5E5E5; 54 | border-bottom-color: #FFC40D; 55 | color: #555555; 56 | cursor: pointer; 57 | } 58 | -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gl2001wl/tcctx/b81c1509e766818e185b77ee97fbc1411905915c/tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gl2001wl/tcctx/b81c1509e766818e185b77ee97fbc1411905915c/tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gl2001wl/tcctx/b81c1509e766818e185b77ee97fbc1411905915c/tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gl2001wl/tcctx/b81c1509e766818e185b77ee97fbc1411905915c/tcctx-console/src/main/resources/console/http/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tcc Tx Index 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 77 | 78 | 79 | 80 |
81 |
82 |
83 |

84 |

85 |
86 |
87 |
88 | 89 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/js/doT.js: -------------------------------------------------------------------------------- 1 | // doT.js 2 | // 2011, Laura Doktorova, https://github.com/olado/doT 3 | // Licensed under the MIT license. 4 | 5 | (function() { 6 | "use strict"; 7 | 8 | var doT = { 9 | version: '1.0.17', 10 | templateSettings: { 11 | evaluate: /\{\{([\s\S]+?\}?)\}\}/g, 12 | interpolate: /\{\{=([\s\S]+?)\}\}/g, 13 | encode: /\{\{!([\s\S]+?)\}\}/g, 14 | use: /\{\{#([\s\S]+?)\}\}/g, 15 | useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g, 16 | define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g, 17 | defineParams:/^\s*([\w$]+):([\s\S]+)/, 18 | conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g, 19 | iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g, 20 | varname: 'it', 21 | strip: true, 22 | append: true, 23 | selfcontained: false 24 | }, 25 | template: undefined, //fn, compile template 26 | compile: undefined //fn, for express 27 | }; 28 | 29 | if (typeof module !== 'undefined' && module.exports) { 30 | module.exports = doT; 31 | } else if (typeof define === 'function' && define.amd) { 32 | define(function(){return doT;}); 33 | } else { 34 | (function(){ return this || (0,eval)('this'); }()).doT = doT; 35 | } 36 | 37 | function encodeHTMLSource() { 38 | var encodeHTMLRules = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' }, 39 | matchHTML = /&(?!#?\w+;)|<|>|"|'|\//g; 40 | return function() { 41 | return this ? this.replace(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : this; 42 | }; 43 | } 44 | String.prototype.encodeHTML = encodeHTMLSource(); 45 | 46 | var startend = { 47 | append: { start: "'+(", end: ")+'", endencode: "||'').toString().encodeHTML()+'" }, 48 | split: { start: "';out+=(", end: ");out+='", endencode: "||'').toString().encodeHTML();out+='"} 49 | }, skip = /$^/; 50 | 51 | function resolveDefs(c, block, def) { 52 | return ((typeof block === 'string') ? block : block.toString()) 53 | .replace(c.define || skip, function(m, code, assign, value) { 54 | if (code.indexOf('def.') === 0) { 55 | code = code.substring(4); 56 | } 57 | if (!(code in def)) { 58 | if (assign === ':') { 59 | if (c.defineParams) value.replace(c.defineParams, function(m, param, v) { 60 | def[code] = {arg: param, text: v}; 61 | }); 62 | if (!(code in def)) def[code]= value; 63 | } else { 64 | new Function("def", "def['"+code+"']=" + value)(def); 65 | } 66 | } 67 | return ''; 68 | }) 69 | .replace(c.use || skip, function(m, code) { 70 | if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) { 71 | if (def[d] && def[d].arg && param) { 72 | var rw = (d+":"+param).replace(/'|\\/g, '_'); 73 | def.__exp = def.__exp || {}; 74 | def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2"); 75 | return s + "def.__exp['"+rw+"']"; 76 | } 77 | }); 78 | var v = new Function("def", "return " + code)(def); 79 | return v ? resolveDefs(c, v, def) : v; 80 | }); 81 | } 82 | 83 | function unescape(code) { 84 | return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, ' '); 85 | } 86 | 87 | doT.template = function(tmpl, c, def) { 88 | c = c || doT.templateSettings; 89 | var cse = c.append ? startend.append : startend.split, needhtmlencode, sid = 0, indv, 90 | str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl; 91 | 92 | str = ("var out='" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g,' ') 93 | .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,''): str) 94 | .replace(/'|\\/g, '\\$&') 95 | .replace(c.interpolate || skip, function(m, code) { 96 | return cse.start + unescape(code) + cse.end; 97 | }) 98 | .replace(c.encode || skip, function(m, code) { 99 | needhtmlencode = true; 100 | return cse.start + unescape(code) + cse.endencode; 101 | }) 102 | .replace(c.conditional || skip, function(m, elsecase, code) { 103 | return elsecase ? 104 | (code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") : 105 | (code ? "';if(" + unescape(code) + "){out+='" : "';}out+='"); 106 | }) 107 | .replace(c.iterate || skip, function(m, iterate, vname, iname) { 108 | if (!iterate) return "';} } out+='"; 109 | sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate); 110 | return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+""); 23 | } 24 | } 25 | 26 | function setCookie(name,value,expires,path,domain,secure) 27 | { 28 | var expDays = expires*24*60*60*1000; 29 | var expDate = new Date(); 30 | expDate.setTime(expDate.getTime()+expDays); 31 | var expString = ((expires==null) ? "": (";expires="+expDate.toGMTString())); 32 | var pathString = ((path==null) ? "": (";path="+path)); 33 | var domainString = ((domain==null) ? "": (";domain="+domain)); 34 | var secureString = ((secure==true) ? ";secure": ""); 35 | document.cookie = name + "="+ escape(value) + expString + pathString + domainString + secureString; 36 | } 37 | 38 | function getCookie(name) 39 | { 40 | var result = null; 41 | var myCookie = document.cookie + ";"; 42 | var searchName = name + "="; 43 | var startOfCookie = myCookie.indexOf(searchName); 44 | var endOfCookie; 45 | if (startOfCookie != -1) 46 | { 47 | startOfCookie += searchName.length; 48 | endOfCookie = myCookie.indexOf(";",startOfCookie); 49 | result = unescape(myCookie.substring(startOfCookie,endOfCookie)); 50 | } 51 | return result; 52 | } 53 | 54 | function setText($obj) { 55 | var key = $obj.attr('langKey'); 56 | if (typeof(lang[key]) != 'undefined') { 57 | var text = lang[key][tcctx.lang.langNow]; 58 | $obj.text(lang[key][tcctx.lang.langNow]); 59 | } else { 60 | log('key [' + key + '] not found'); 61 | } 62 | } 63 | function setTitle($obj) { 64 | var key = $obj.attr('langKey'); 65 | if (typeof(lang[key]) != 'undefined') { 66 | var title = lang[key][tcctx.lang.langNow]; 67 | $obj.attr('title', title); 68 | } else { 69 | log('key [' + key + '] not found'); 70 | } 71 | } 72 | 73 | return { 74 | langNow : LANG_CN, 75 | EVENT_LOAD_FINISHED : 'loadFinished', 76 | init : function(langNow) { 77 | if (typeof(langNow) != 'undefined') { 78 | this.setLangType(langNow); 79 | } else { 80 | var langInCookie = getCookie(COOKIE_LANG_NAME); 81 | if (langInCookie == LANG_CN || langInCookie == LANG_EN) { 82 | this.setLangType(langInCookie); 83 | } 84 | } 85 | $(document).on(this.EVENT_LOAD_FINISHED, '.lang', function() { 86 | log('load lang'); 87 | setText($(this)); 88 | }); 89 | $(document).on(this.EVENT_LOAD_FINISHED, '.langTitle', function() { 90 | log('load title'); 91 | setTitle($(this)); 92 | }); 93 | this.trigger(); 94 | 95 | $(document).on('click','.langSelector',function() { 96 | var langSelected = $(this).attr('langNow'); 97 | tcctx.lang.setLangType(langSelected); 98 | tcctx.lang.trigger(); 99 | return false; 100 | }); 101 | }, 102 | setLangType : function (langNow) { 103 | this.langNow = langNow; 104 | setCookie(COOKIE_LANG_NAME,langNow,30,'/'); 105 | }, 106 | getLangType : function () { 107 | return this.langNow; 108 | }, 109 | show : function($parent) { 110 | var $obj; 111 | var $objTitle; 112 | if ($parent) { 113 | $obj = $parent.find('.lang'); 114 | $objTitle = $parent.find('.langTitle'); 115 | } else { 116 | $obj = $('.lang'); 117 | $objTitle = $('.langTitle'); 118 | } 119 | $obj.each(function() { 120 | setText($(this)); 121 | }); 122 | $objTitle.each(function() { 123 | setTitle($(this)); 124 | }); 125 | }, 126 | trigger : function() { 127 | log('to load lang now'); 128 | $('.lang').trigger(this.EVENT_LOAD_FINISHED);//触发语言显示事件 129 | $('.langTitle').trigger(this.EVENT_LOAD_FINISHED);//触发语言显示事件 130 | } 131 | } 132 | }(); 133 | -------------------------------------------------------------------------------- /tcctx-console/src/main/resources/console/http/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /tcctx-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | tcc 7 | com.jd.tx 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | tcctx-core 13 | 14 | 15 | 16 | org.springframework 17 | spring 18 | 2.5.6 19 | 20 | 21 | 22 | org.hsqldb 23 | hsqldb 24 | 2.3.3 25 | test 26 | 27 | 28 | org.slf4j 29 | jcl-over-slf4j 30 | ${slf4j.version} 31 | test 32 | 33 | 34 | org.slf4j 35 | log4j-over-slf4j 36 | ${slf4j.version} 37 | test 38 | 39 | 40 | ch.qos.logback 41 | logback-classic 42 | ${logback.version} 43 | test 44 | 45 | 46 | 47 | org.dbunit 48 | dbunit 49 | 2.5.1 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-source-plugin 60 | ${maven-source-plugin.version} 61 | 62 | 63 | attach-sources 64 | verify 65 | 66 | jar-no-fork 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/ResourceItem.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import com.google.common.collect.BiMap; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author Leon Guo 9 | * Creation Date: 2016/2/16 10 | */ 11 | public interface ResourceItem { 12 | 13 | /** 14 | * Return true if has try action 15 | * @return 16 | */ 17 | boolean hasTry(); 18 | 19 | /** 20 | * Return true if has cancel action 21 | * @return 22 | */ 23 | boolean hasCancel(); 24 | 25 | /** 26 | * Try transaction resource 27 | * @param context 28 | */ 29 | void tryTx(TransactionContext context); 30 | 31 | /** 32 | * Confirm transaction resource 33 | * @param context 34 | */ 35 | void confirmTx(TransactionContext context); 36 | 37 | /** 38 | * Cancel transaction resource 39 | * @param context 40 | */ 41 | void cancelTx(TransactionContext context); 42 | 43 | /** 44 | * Return a map which contains the relationship of state and if needing update it after it. 45 | * @return 46 | */ 47 | List getIgnoreUpdateState(); 48 | 49 | /** 50 | * Return the mapping of real state and state key in business system. 51 | * @return 52 | */ 53 | BiMap getStateMapping(); 54 | 55 | /** 56 | * Set state mapping values 57 | * @param stateMapping 58 | */ 59 | void setStateMapping(BiMap stateMapping); 60 | 61 | /** 62 | * Return the index for generate state code, use the index in resource manager if return null. 63 | * @return 64 | */ 65 | Integer getStateIndex(); 66 | 67 | /** 68 | * Transaction state for getIfUpdateStateAfterAction() 69 | */ 70 | enum State { 71 | begin, trySuccess, tryFailed, confirmSuccess, confirmFailed, cancelSuccess, cancelFailed 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/ResourceItemLinkedList.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import lombok.*; 4 | 5 | import java.util.Iterator; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Leon Guo 10 | * Creation Date: 2016/4/26 11 | */ 12 | @AllArgsConstructor 13 | @Getter 14 | @Setter 15 | public class ResourceItemLinkedList implements Iterable { 16 | 17 | private ResourceItemLinkedList pre; 18 | 19 | private ResourceItem item; 20 | 21 | private ResourceItemLinkedList next; 22 | 23 | public boolean hasPre() { 24 | return pre != null; 25 | } 26 | 27 | public boolean hasNext() { 28 | return next != null; 29 | } 30 | 31 | public static ResourceItemLinkedList build(List resourceItems) { 32 | Object[] objects = resourceItems.toArray(); 33 | ResourceItemLinkedList first = null; 34 | ResourceItemLinkedList linkedList = null; 35 | for (int i = 0; i < objects.length; i++) { 36 | ResourceItem resourceItem = (ResourceItem) objects[i]; 37 | if (i == 0) { 38 | linkedList = new ResourceItemLinkedList(null, resourceItem, null); 39 | first = linkedList; 40 | continue; 41 | } 42 | linkedList.next = new ResourceItemLinkedList(linkedList, resourceItem, null); 43 | linkedList = linkedList.next; 44 | } 45 | return first; 46 | } 47 | 48 | public ResourceItemLinkedList getHead() { 49 | ResourceItemLinkedList linkedList = this; 50 | while (linkedList.hasPre()) { 51 | linkedList = linkedList.getPre(); 52 | } 53 | return linkedList; 54 | } 55 | 56 | public ResourceItemLinkedList getTail() { 57 | ResourceItemLinkedList linkedList = this; 58 | while (linkedList.hasNext()) { 59 | linkedList = linkedList.getNext(); 60 | } 61 | return linkedList; 62 | } 63 | 64 | @Override 65 | public Iterator iterator() { 66 | return null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/StateGenerator.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Leon Guo 7 | * Creation Date: 2016/3/10 8 | */ 9 | public interface StateGenerator { 10 | 11 | void generatorStates(List resourceItems); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/TransactionContext.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import javax.sql.DataSource; 4 | 5 | /** 6 | * @author Leon Guo 7 | * Creation Date: 2016/2/16 8 | */ 9 | public interface TransactionContext { 10 | 11 | /** 12 | * The data source which save transaction state. 13 | * @return 14 | */ 15 | DataSource getDataSource(); 16 | 17 | /** 18 | * The key of this transaction group. 19 | * @return 20 | */ 21 | String getKey(); 22 | 23 | /** 24 | * The unique id of current transaction process. 25 | * @return 26 | */ 27 | String getId(); 28 | 29 | /** 30 | * Current state. 31 | * @return 32 | */ 33 | String getState(); 34 | 35 | /** 36 | * Return the resource object of current transaction. 37 | * @return 38 | */ 39 | T getResourceObject(); 40 | 41 | void setResourceObject(T resourceObject); 42 | 43 | /** 44 | * Set current state 45 | * @param state 46 | */ 47 | void setState(String state); 48 | 49 | /** 50 | * Return the time out of transaction, need be retried in other async process. 51 | * @return 52 | */ 53 | // int getTimeoutForRetry(); 54 | 55 | /** 56 | * The max rows could be loaded for once in retry process. 57 | * @return 58 | */ 59 | // int getSelectLimitForRetry(); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/TransactionManager.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import lombok.NonNull; 4 | import lombok.Setter; 5 | import org.apache.commons.lang3.Validate; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @author Leon Guo 11 | * Creation Date: 2016/2/16 12 | */ 13 | public class TransactionManager { 14 | 15 | @Setter 16 | private Map resourcesMap; 17 | 18 | public TransactionResource getResource(@NonNull String key) { 19 | Validate.notEmpty(resourcesMap); 20 | return resourcesMap.get(key); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/TransactionResource.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import lombok.Data; 4 | import org.apache.commons.collections4.CollectionUtils; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author Leon Guo 10 | * Creation Date: 2016/2/16 11 | */ 12 | @Data 13 | public class TransactionResource { 14 | 15 | private List resourceItems; 16 | 17 | private String table; 18 | 19 | private String msgCol; 20 | 21 | private int msgMaxLength; 22 | 23 | private String stateCol; 24 | 25 | private String handleTimeCol; 26 | 27 | private String idCol; 28 | 29 | private StateGenerator stateGenerator; 30 | 31 | /** 32 | * If need to automatically update state by TCC framework 33 | */ 34 | private boolean updateState = true; 35 | 36 | /** 37 | * Return the beginning state of first tx item. 38 | * Return null if doesn't have tx items. 39 | * @return 40 | */ 41 | public String getBeginningState() { 42 | if (CollectionUtils.isEmpty(resourceItems) || resourceItems.get(0).getStateMapping() == null) { 43 | return null; 44 | } 45 | return (String) resourceItems.get(0).getStateMapping().get(ResourceItem.State.begin); 46 | } 47 | 48 | /** 49 | * If use this bean spring, should invoke it in init-method, or invoke it in the very beginning. 50 | */ 51 | public void init() { 52 | //If has stateGenerator, then generator state for resource items. 53 | if (stateGenerator != null && CollectionUtils.isNotEmpty(resourceItems)) { 54 | stateGenerator.generatorStates(resourceItems); 55 | } 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/TransactionRunner.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | /** 4 | * @author Leon Guo 5 | * Creation Date: 2016/2/16 6 | */ 7 | public interface TransactionRunner { 8 | 9 | /** 10 | * Begin execute a TCC transaction 11 | * @param context 12 | */ 13 | void run(TransactionContext context); 14 | 15 | /** 16 | * Set the transactionManager before execute 17 | * @param transactionManager 18 | */ 19 | void setTransactionManager(TransactionManager transactionManager); 20 | 21 | /** 22 | * Returen transactionManager 23 | * @return 24 | */ 25 | TransactionManager getTransactionManager(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/entity/TransactionEntity.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | * @author Leon Guo 10 | * Creation Date: 2016/4/11 11 | */ 12 | @Data 13 | public class TransactionEntity implements Serializable { 14 | 15 | private String id; 16 | 17 | private String state; 18 | 19 | private Date handleTime; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/exception/SOATxUnawareException.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.exception; 2 | 3 | /** 4 | * Unaware exception happened, 5 | * can't know current resource item acton successful or not, 6 | * need retry until get real state. 7 | * @author Leon Guo 8 | * Creation Date: 2016/2/16 9 | */ 10 | public class SOATxUnawareException extends RuntimeException { 11 | 12 | public SOATxUnawareException() { 13 | } 14 | 15 | public SOATxUnawareException(Throwable e) { 16 | super(e); 17 | } 18 | 19 | public SOATxUnawareException(String msg) { 20 | super(msg); 21 | } 22 | 23 | public SOATxUnawareException(String msg, Throwable e) { 24 | super(msg, e); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/exception/SOATxUnrecoverableException.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.exception; 2 | 3 | /** 4 | * Unrecoverable exception happened, 5 | * means need cancel all executed items directly 6 | * and return failed. 7 | * @author Leon Guo 8 | * Creation Date: 2016/2/17 9 | */ 10 | public class SOATxUnrecoverableException extends RuntimeException{ 11 | 12 | public SOATxUnrecoverableException() { 13 | } 14 | 15 | public SOATxUnrecoverableException(Throwable e) { 16 | super(e); 17 | } 18 | 19 | public SOATxUnrecoverableException(String msg) { 20 | super(msg); 21 | } 22 | 23 | public SOATxUnrecoverableException(String msg, Throwable e) { 24 | super(msg, e); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/impl/CommonTransactionContext.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.impl; 2 | 3 | import com.jd.tx.tcc.core.TransactionContext; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import javax.sql.DataSource; 9 | 10 | /** 11 | * @author Leon Guo 12 | * Creation Date: 2016/2/17 13 | */ 14 | @EqualsAndHashCode 15 | @ToString 16 | public class CommonTransactionContext implements TransactionContext { 17 | 18 | @Setter private DataSource dataSource; 19 | 20 | @Setter private String key; 21 | 22 | @Setter private String id; 23 | 24 | private String state; 25 | 26 | private T resourceObject; 27 | 28 | @Setter private int timeoutForRetry; 29 | 30 | @Setter private int selectLimitForRetry; 31 | 32 | public CommonTransactionContext() { 33 | } 34 | 35 | @Override 36 | public DataSource getDataSource() { 37 | return dataSource; 38 | } 39 | 40 | @Override 41 | public String getKey() { 42 | return key; 43 | } 44 | 45 | @Override 46 | public String getId() { 47 | return id; 48 | } 49 | 50 | @Override 51 | public String getState() { 52 | return state; 53 | } 54 | 55 | @Override 56 | public T getResourceObject() { 57 | return resourceObject; 58 | } 59 | 60 | @Override 61 | public void setResourceObject(T resourceObject) { 62 | this.resourceObject = resourceObject; 63 | } 64 | 65 | @Override 66 | public void setState(String state) { 67 | this.state = state; 68 | } 69 | 70 | // @Override 71 | // public int getTimeoutForRetry() { 72 | // return timeoutForRetry; 73 | // } 74 | // 75 | // @Override 76 | // public int getSelectLimitForRetry() { 77 | // return selectLimitForRetry; 78 | // } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/impl/JDBCHelper.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.impl; 2 | 3 | import com.jd.tx.tcc.core.TransactionContext; 4 | import com.jd.tx.tcc.core.TransactionResource; 5 | import com.jd.tx.tcc.core.entity.TransactionEntity; 6 | import com.jd.tx.tcc.core.query.TransactionQuery; 7 | import lombok.NonNull; 8 | import org.apache.commons.collections4.CollectionUtils; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.apache.commons.lang3.Validate; 11 | import org.springframework.jdbc.core.JdbcTemplate; 12 | import org.springframework.jdbc.core.RowMapper; 13 | 14 | import java.sql.ResultSet; 15 | import java.sql.SQLException; 16 | import java.util.ArrayList; 17 | import java.util.Date; 18 | import java.util.List; 19 | 20 | /** 21 | * @author Leon Guo 22 | * Creation Date: 2016/2/17 23 | */ 24 | public class JDBCHelper { 25 | 26 | /** 27 | * Default timeout is 2 minutes. 28 | */ 29 | private static final int DEFAULT_TIMEOUT = 2; 30 | 31 | /** 32 | * Default max retry rows for once is 100. 33 | */ 34 | private static final int DEFAULT_RETRY_ROWS = 100; 35 | 36 | /** 37 | * Update the state of current transaction.
38 | *

Shouldn't throw SOATxUnrecoverableException. 39 | * If updateState state failed, just wait for retry. 40 | *

41 | * @param context 42 | * @param resource 43 | * @param state 44 | */ 45 | public static void updateState(@NonNull TransactionContext context, 46 | @NonNull TransactionResource resource, 47 | @NonNull String state, 48 | String msg) { 49 | Validate.notNull(context.getDataSource()); 50 | Validate.notNull(context.getId()); 51 | Validate.notNull(resource.getTable()); 52 | Validate.notNull(resource.getIdCol()); 53 | Validate.notNull(resource.getStateCol()); 54 | 55 | List paramList = new ArrayList(4); 56 | StringBuilder sql = new StringBuilder() 57 | .append("update ") 58 | .append(resource.getTable()) 59 | .append(" set ") 60 | .append(resource.getStateCol()) 61 | .append(" = ?"); 62 | paramList.add(state); 63 | if (StringUtils.isNotBlank(resource.getMsgCol()) 64 | && StringUtils.isNotEmpty(msg)) { 65 | //updateState msg column. 66 | sql.append(", ") 67 | .append(resource.getMsgCol()) 68 | .append(" = ?"); 69 | if (resource.getMsgMaxLength() > 0) { 70 | msg = msg.length() > resource.getMsgMaxLength() ? msg.substring(0, resource.getMsgMaxLength()) : msg; 71 | } 72 | paramList.add(msg); 73 | } 74 | if (StringUtils.isNotBlank(resource.getHandleTimeCol())) { 75 | //updateState handle time column to current time. 76 | sql.append(", ") 77 | .append(resource.getHandleTimeCol()) 78 | .append(" = ?"); 79 | paramList.add(new Date()); 80 | } 81 | sql.append(" where ").append(resource.getIdCol()).append(" = ?"); 82 | paramList.add(context.getId()); 83 | 84 | //use spring jdbc template to updateState the state 85 | new JdbcTemplate(context.getDataSource()).update(sql.toString(), paramList.toArray()); 86 | } 87 | 88 | /** 89 | * Return the entities of timeout transactions. 90 | * @param query 91 | * @return 92 | */ 93 | public static List findTimeoutItems(@NonNull TransactionQuery query) { 94 | TransactionContext context = query.getContext(); 95 | Validate.notNull(context); 96 | Validate.notNull(context.getDataSource()); 97 | 98 | TransactionResource resource = query.getResource(); 99 | Validate.notNull(resource); 100 | Validate.notNull(resource.getTable()); 101 | Validate.notNull(resource.getIdCol()); 102 | Validate.notNull(resource.getStateCol()); 103 | Validate.notNull(resource.getHandleTimeCol()); 104 | 105 | StringBuilder sql = new StringBuilder() 106 | .append("select ") 107 | .append(resource.getIdCol()) 108 | .append(", ") 109 | .append(resource.getStateCol()) 110 | .append(", ") 111 | .append(resource.getHandleTimeCol()) 112 | .append(" from ") 113 | .append(resource.getTable()) 114 | .append(" where ") 115 | .append(resource.getHandleTimeCol()) 116 | .append(" < date_sub(now(), interval ? minute)") 117 | .append(" and ") 118 | .append(resource.getStateCol()) 119 | .append(" IS NOT NULL"); 120 | 121 | if (CollectionUtils.isNotEmpty(query.getExcludeStates())) { 122 | sql.append(" and ") 123 | .append(resource.getStateCol()) 124 | .append(" not in ('") 125 | .append(StringUtils.join(query.getExcludeStates(), "','")) 126 | .append("')"); 127 | } 128 | if (CollectionUtils.isNotEmpty(query.getIncludeStates())) { 129 | sql.append(" and ") 130 | .append(resource.getStateCol()) 131 | .append(" in ('") 132 | .append(StringUtils.join(query.getIncludeStates(), "','")) 133 | .append("')"); 134 | } 135 | if (query.getShardingCount() > 1) { 136 | // Need sharding, got mode from sharding count. 137 | sql.append(" and MOD(UNIX_TIMESTAMP(").append(resource.getHandleTimeCol()) 138 | .append("), ?) in ("); 139 | char split = 0; 140 | for (int shardingItem : query.getShardingItems()) { 141 | sql.append(split == 0 ? "" : split).append("?"); 142 | split = ','; 143 | } 144 | sql.append(")"); 145 | } 146 | if (StringUtils.isNotBlank(query.getLastId())) { 147 | sql.append(" and ") 148 | .append(resource.getIdCol()) 149 | .append(" > ?"); 150 | } 151 | sql.append(" order by ") 152 | .append(resource.getIdCol()) 153 | .append(" limit ?"); 154 | 155 | List paramList = new ArrayList<>(); 156 | paramList.add(query.getMinutesBefore() < 1 ? DEFAULT_TIMEOUT : query.getMinutesBefore()); 157 | if (query.getShardingCount() > 1) { 158 | paramList.add(query.getShardingCount()); 159 | for (Integer shardingItem : query.getShardingItems()) { 160 | paramList.add(shardingItem); 161 | } 162 | } 163 | if (StringUtils.isNotBlank(query.getLastId())) { 164 | paramList.add(query.getLastId()); 165 | } 166 | paramList.add(query.getQueryRows() < 1 ? DEFAULT_RETRY_ROWS : query.getQueryRows()); 167 | 168 | List list = 169 | new JdbcTemplate(context.getDataSource()).query(sql.toString(), 170 | paramList.toArray(), 171 | new RowMapper() { 172 | 173 | @Override 174 | public TransactionEntity mapRow(ResultSet rs, int rowNum) throws SQLException { 175 | TransactionEntity entity = new TransactionEntity(); 176 | entity.setId(rs.getString(1)); 177 | entity.setState(rs.getString(2)); 178 | entity.setHandleTime(rs.getDate(3)); 179 | return entity; 180 | } 181 | 182 | }); 183 | 184 | return list; 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/impl/SeqStateGenerator.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.impl; 2 | 3 | import com.google.common.collect.BiMap; 4 | import com.google.common.collect.ImmutableBiMap; 5 | import com.jd.tx.tcc.core.ResourceItem; 6 | import com.jd.tx.tcc.core.StateGenerator; 7 | import org.apache.commons.lang3.Validate; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author Leon Guo 13 | * Creation Date: 2016/3/10 14 | */ 15 | public class SeqStateGenerator implements StateGenerator { 16 | 17 | @Override 18 | public void generatorStates(List resourceItems) { 19 | Validate.notNull(resourceItems); 20 | 21 | for (int i = 1; i <= resourceItems.size(); i++) { 22 | ResourceItem resourceItem = resourceItems.get(i - 1); 23 | int index = resourceItem.getStateIndex() == null ? i : resourceItem.getStateIndex(); 24 | BiMap stateMap = new ImmutableBiMap.Builder() 25 | .put(ResourceItem.State.begin, (index * 10) + "0") 26 | .put(ResourceItem.State.trySuccess, (index * 10 + 1) + "1") 27 | .put(ResourceItem.State.tryFailed, (index * 10 + 1) + "0") 28 | .put(ResourceItem.State.confirmSuccess, (index * 10 + 2) + "1") 29 | .put(ResourceItem.State.confirmFailed, (index * 10 + 2) + "0") 30 | .put(ResourceItem.State.cancelSuccess, (index * 10 + 3) + "1") 31 | .put(ResourceItem.State.cancelFailed, (index * 10 + 3) + "0") 32 | .build(); 33 | resourceItem.setStateMapping(stateMap); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/query/TransactionQuery.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.query; 2 | 3 | import com.jd.tx.tcc.core.ResourceItem; 4 | import com.jd.tx.tcc.core.TransactionContext; 5 | import com.jd.tx.tcc.core.TransactionResource; 6 | import com.jd.tx.tcc.core.entity.TransactionEntity; 7 | import com.jd.tx.tcc.core.impl.JDBCHelper; 8 | import lombok.Getter; 9 | import lombok.NonNull; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * @author Leon Guo 16 | * Creation Date: 2016/4/11 17 | */ 18 | public class TransactionQuery { 19 | 20 | @Getter 21 | private TransactionContext context; 22 | 23 | @Getter 24 | private TransactionResource resource; 25 | 26 | @Getter 27 | private String lastId; 28 | 29 | @Getter 30 | private int queryRows = 100; 31 | 32 | @Getter 33 | private int minutesBefore = 2; 34 | 35 | @Getter 36 | private int shardingCount = -1; 37 | 38 | @Getter 39 | private List shardingItems; 40 | 41 | @Getter 42 | private List excludeStates; 43 | 44 | @Getter 45 | private List includeStates; 46 | 47 | public TransactionQuery(@NonNull TransactionContext context, @NonNull TransactionResource resource) { 48 | this.context = context; 49 | this.resource = resource; 50 | } 51 | 52 | public TransactionQuery setLastId(String lastId) { 53 | this.lastId = lastId; 54 | return this; 55 | } 56 | 57 | public TransactionQuery setQueryRows(int queryRows) { 58 | this.queryRows = queryRows; 59 | return this; 60 | } 61 | 62 | public TransactionQuery setMinutesBefore(int minutesBefore) { 63 | this.minutesBefore = minutesBefore; 64 | return this; 65 | } 66 | 67 | public TransactionQuery setSharding(int shardingCount, List shardingItems) { 68 | this.shardingCount = shardingCount; 69 | this.shardingItems = shardingItems; 70 | return this; 71 | } 72 | 73 | public TransactionQuery setExcludeStates(List excludeStates) { 74 | this.excludeStates = excludeStates; 75 | return this; 76 | } 77 | 78 | public TransactionQuery setIncludeStates(List includeStates) { 79 | this.includeStates = includeStates; 80 | return this; 81 | } 82 | 83 | public List query() { 84 | return JDBCHelper.findTimeoutItems(this); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/retry/RetryJob.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.retry; 2 | 3 | import com.jd.tx.tcc.core.ResourceItem; 4 | import com.jd.tx.tcc.core.TransactionManager; 5 | import com.jd.tx.tcc.core.TransactionResource; 6 | import com.jd.tx.tcc.core.TransactionRunner; 7 | import com.jd.tx.tcc.core.entity.TransactionEntity; 8 | import com.jd.tx.tcc.core.impl.CommonTransactionContext; 9 | import com.jd.tx.tcc.core.query.TransactionQuery; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.Setter; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.apache.commons.collections4.CollectionUtils; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.apache.commons.lang3.Validate; 16 | 17 | import javax.sql.DataSource; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.concurrent.*; 21 | 22 | /** 23 | * An execution scheduler for retry timeout tx job. 24 | * @author Leon Guo 25 | * Creation Date: 2016/5/17 26 | */ 27 | @Slf4j 28 | @RequiredArgsConstructor 29 | public class RetryJob { 30 | 31 | private final ScheduledExecutorService scheduler = 32 | Executors.newScheduledThreadPool(1); 33 | 34 | private TransactionRunner transactionRunner; 35 | 36 | private String key; 37 | 38 | private DataSource dataSource; 39 | 40 | @Setter 41 | private long initialDelay = 0; 42 | 43 | @Setter 44 | private long period = 2; 45 | 46 | @Setter 47 | private TimeUnit scheduleTimeUnit = TimeUnit.MINUTES; 48 | 49 | @Setter 50 | private int fetchRows = 200; 51 | 52 | @Setter 53 | private int timeoutMinutes = 2; 54 | 55 | @Setter 56 | private List includeStates; 57 | 58 | @Setter 59 | private List excludeStates; 60 | 61 | private String lastId = null; 62 | 63 | public void start() { 64 | Validate.notEmpty(key); 65 | Validate.notNull(dataSource); 66 | Validate.notNull(transactionRunner); 67 | 68 | scheduler.scheduleAtFixedRate(new RetryRunner(), initialDelay, period, scheduleTimeUnit); 69 | } 70 | 71 | class RetryRunner implements Runnable { 72 | 73 | @Override 74 | public void run() { 75 | List timeoutTx = fetchData(); 76 | while (CollectionUtils.isNotEmpty(timeoutTx)) { 77 | executeTx(timeoutTx); 78 | timeoutTx = fetchData(); 79 | } 80 | } 81 | 82 | private List fetchData() { 83 | TransactionManager transactionManager = transactionRunner.getTransactionManager(); 84 | TransactionResource resource = transactionManager.getResource(key); 85 | 86 | CommonTransactionContext context = new CommonTransactionContext(); 87 | context.setKey(key); 88 | context.setDataSource(dataSource); 89 | 90 | TransactionQuery query = new TransactionQuery(context, resource) 91 | .setMinutesBefore(timeoutMinutes) 92 | .setQueryRows(fetchRows); 93 | if (StringUtils.isNotBlank(lastId)) { 94 | query.setLastId(lastId); 95 | } 96 | if (CollectionUtils.isNotEmpty(includeStates)) { 97 | query.setIncludeStates(includeStates); 98 | } else if (CollectionUtils.isNotEmpty(excludeStates)) { 99 | query.setExcludeStates(excludeStates); 100 | } else { 101 | query.setExcludeStates(buildExcludeStates(resource.getResourceItems())); 102 | } 103 | List timeoutItems = query.query(); 104 | 105 | if (CollectionUtils.isNotEmpty(timeoutItems)) { 106 | lastId = timeoutItems.get(timeoutItems.size() - 1).getId(); 107 | } else { 108 | //If no more data, set the lastId to null, make it load data from the very beginning in the next schedule time. 109 | lastId = null; 110 | } 111 | return timeoutItems; 112 | } 113 | 114 | private void executeTx(List data) { 115 | if (CollectionUtils.isEmpty(data)) { 116 | return; 117 | } 118 | 119 | for (TransactionEntity entity : data) { 120 | CommonTransactionContext txContext = new CommonTransactionContext(); 121 | 122 | txContext.setKey(key); 123 | txContext.setDataSource(dataSource); 124 | txContext.setId(entity.getId()); 125 | txContext.setState(entity.getState()); 126 | 127 | try { 128 | transactionRunner.run(txContext); 129 | } catch (Throwable e) { 130 | log.error(e.getMessage(), e); 131 | } 132 | } 133 | } 134 | 135 | private List buildExcludeStates(List resourceItems) { 136 | List excludeStates = new ArrayList<>(); 137 | if (resourceItems.get(resourceItems.size() - 1).getStateMapping().get(ResourceItem.State.confirmSuccess) != null) { 138 | excludeStates.add((String) resourceItems.get(resourceItems.size() - 1).getStateMapping().get(ResourceItem.State.confirmSuccess)); 139 | } 140 | if (resourceItems.get(0).getStateMapping().get(ResourceItem.State.cancelSuccess) != null) { 141 | excludeStates.add((String) resourceItems.get(0).getStateMapping().get(ResourceItem.State.cancelSuccess)); 142 | } 143 | return excludeStates; 144 | } 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/state/BeginState.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.state; 2 | 3 | /** 4 | * @author Leon Guo 5 | * Creation Date: 2016/4/26 6 | */ 7 | public class BeginState implements TransactionState { 8 | 9 | public static final BeginState instance = new BeginState(); 10 | 11 | @Override 12 | public void handle(StateContext stateContext) { 13 | stateContext.moveToHead(); 14 | stateContext.setState(TryState.instance); 15 | stateContext.invokeState(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/state/CancelState.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.state; 2 | 3 | import com.jd.tx.tcc.core.ResourceItem; 4 | import com.jd.tx.tcc.core.ResourceItemLinkedList; 5 | import com.jd.tx.tcc.core.exception.SOATxUnawareException; 6 | import org.apache.commons.collections4.CollectionUtils; 7 | 8 | /** 9 | * @author Leon Guo 10 | * Creation Date: 2016/4/26 11 | */ 12 | public class CancelState implements TransactionState { 13 | 14 | public static final CancelState instance = new CancelState(); 15 | 16 | @Override 17 | public void handle(StateContext stateContext) { 18 | try { 19 | ResourceItemLinkedList linkedItem = stateContext.getItemLinkedList(); 20 | 21 | if (linkedItem.getItem().getStateMapping() == null || 22 | !linkedItem.getItem().getStateMapping().containsKey(ResourceItem.State.cancelSuccess) || 23 | !linkedItem.getItem().getStateMapping().get(ResourceItem.State.cancelSuccess) 24 | .equals( 25 | stateContext.getTransactionContext().getState() 26 | )) { 27 | TransactionAction.cancelAction.action(stateContext.getTransactionContext(), stateContext.getResource(), linkedItem.getItem()); 28 | } 29 | stateContext.getTransactionContext().setState(null); 30 | 31 | while (linkedItem.hasPre()) { 32 | linkedItem = linkedItem.getPre(); 33 | stateContext.setItemLinkedList(linkedItem); 34 | TransactionAction.cancelAction.action(stateContext.getTransactionContext(), stateContext.getResource(), linkedItem.getItem()); 35 | } 36 | } catch (Throwable e) { 37 | //unaware exception happened, shut down process and wait for retry. 38 | throw new SOATxUnawareException(e.getMessage(), e); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/state/ConfirmState.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.state; 2 | 3 | import com.jd.tx.tcc.core.ResourceItem; 4 | import com.jd.tx.tcc.core.ResourceItemLinkedList; 5 | import com.jd.tx.tcc.core.exception.SOATxUnawareException; 6 | import com.jd.tx.tcc.core.exception.SOATxUnrecoverableException; 7 | 8 | /** 9 | * @author Leon Guo 10 | * Creation Date: 2016/4/26 11 | */ 12 | public class ConfirmState implements TransactionState { 13 | 14 | public static final ConfirmState instance = new ConfirmState(); 15 | 16 | @Override 17 | public void handle(StateContext stateContext) { 18 | try { 19 | ResourceItemLinkedList linkedItem = stateContext.getItemLinkedList(); 20 | 21 | if (linkedItem.getItem().getStateMapping() == null || 22 | !linkedItem.getItem().getStateMapping().containsKey(ResourceItem.State.confirmSuccess) || 23 | !linkedItem.getItem().getStateMapping().get(ResourceItem.State.confirmSuccess) 24 | .equals( 25 | stateContext.getTransactionContext().getState() 26 | )) { 27 | TransactionAction.confirmAction.action(stateContext.getTransactionContext(), stateContext.getResource(), linkedItem.getItem()); 28 | } 29 | stateContext.getTransactionContext().setState(null); 30 | 31 | while (linkedItem.hasNext()) { 32 | linkedItem = linkedItem.getNext(); 33 | stateContext.setItemLinkedList(linkedItem); 34 | TransactionAction.confirmAction.action(stateContext.getTransactionContext(), stateContext.getResource(), linkedItem.getItem()); 35 | } 36 | } catch (SOATxUnrecoverableException e) { 37 | //unrecoverable exception happened, cancel all. 38 | stateContext.moveToTail(); 39 | stateContext.setState(CancelState.instance); 40 | stateContext.invokeState(); 41 | //tell the client this transaction failed. 42 | throw e; 43 | } catch (Throwable e) { 44 | //unaware exception happened, shut down process and wait for retry. 45 | throw new SOATxUnawareException(e.getMessage(), e); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/state/StateContext.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.state; 2 | 3 | import com.jd.tx.tcc.core.ResourceItemLinkedList; 4 | import com.jd.tx.tcc.core.TransactionContext; 5 | import com.jd.tx.tcc.core.TransactionResource; 6 | import lombok.*; 7 | 8 | /** 9 | * @author Leon Guo 10 | * Creation Date: 2016/4/26 11 | */ 12 | @Getter 13 | @Setter 14 | @RequiredArgsConstructor 15 | public class StateContext { 16 | 17 | @NonNull 18 | private TransactionContext transactionContext; 19 | 20 | @NonNull 21 | private TransactionResource resource; 22 | 23 | @NonNull 24 | private ResourceItemLinkedList itemLinkedList; 25 | 26 | @NonNull 27 | private TransactionState state; 28 | 29 | public void invokeState() { 30 | if (state != null) { 31 | state.handle(this); 32 | } 33 | } 34 | 35 | public void moveToHead() { 36 | if (itemLinkedList != null) { 37 | itemLinkedList = itemLinkedList.getHead(); 38 | } 39 | } 40 | 41 | public void moveToTail() { 42 | if (itemLinkedList != null) { 43 | itemLinkedList = itemLinkedList.getTail(); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/state/TransactionAction.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.state; 2 | 3 | 4 | import com.jd.tx.tcc.core.ResourceItem; 5 | import com.jd.tx.tcc.core.TransactionContext; 6 | import com.jd.tx.tcc.core.TransactionResource; 7 | import com.jd.tx.tcc.core.exception.SOATxUnrecoverableException; 8 | import com.jd.tx.tcc.core.impl.JDBCHelper; 9 | 10 | /** 11 | * @author Leon Guo 12 | * Creation Date: 2016/2/17 13 | */ 14 | interface TransactionAction { 15 | 16 | TryAction tryAction = new TryAction(); 17 | 18 | ConfirmAction confirmAction = new ConfirmAction(); 19 | 20 | CancelAction cancelAction = new CancelAction(); 21 | 22 | void action(TransactionContext context, TransactionResource resource, ResourceItem item) throws Throwable; 23 | 24 | } 25 | 26 | class TryAction implements TransactionAction { 27 | 28 | @Override 29 | public void action(TransactionContext context, TransactionResource resource, ResourceItem item) throws Throwable { 30 | try { 31 | if (item.hasTry()) { 32 | item.tryTx(context); 33 | } 34 | } catch (SOATxUnrecoverableException e) { 35 | //when unrecoverable exception happened, updateState the state to try failed for canceling all before it. 36 | if (resource.isUpdateState() && (item.getIgnoreUpdateState() == null || !item.getIgnoreUpdateState().contains(ResourceItem.State.tryFailed))) { 37 | JDBCHelper.updateState(context, resource, (String) item.getStateMapping().get(ResourceItem.State.tryFailed), e.getMessage()); 38 | } 39 | throw e; 40 | } catch (Throwable e) { 41 | throw e; 42 | } 43 | if (resource.isUpdateState() && (item.getIgnoreUpdateState() == null || !item.getIgnoreUpdateState().contains(ResourceItem.State.trySuccess))) { 44 | JDBCHelper.updateState(context, resource, (String) item.getStateMapping().get(ResourceItem.State.trySuccess), null); 45 | } 46 | } 47 | 48 | } 49 | 50 | class ConfirmAction implements TransactionAction { 51 | 52 | @Override 53 | public void action(TransactionContext context, TransactionResource resource, ResourceItem item) throws Throwable { 54 | try { 55 | item.confirmTx(context); 56 | } catch (SOATxUnrecoverableException e) { 57 | //when unrecoverable exception happened, updateState the state to confirm failed for canceling all. 58 | if (resource.isUpdateState() && (item.getIgnoreUpdateState() == null || !item.getIgnoreUpdateState().contains(ResourceItem.State.confirmFailed))) { 59 | JDBCHelper.updateState(context, resource, (String) item.getStateMapping().get(ResourceItem.State.confirmFailed), e.getMessage()); 60 | } 61 | throw e; 62 | } catch (Throwable e) { 63 | throw e; 64 | } 65 | if (resource.isUpdateState() && (item.getIgnoreUpdateState() == null || !item.getIgnoreUpdateState().contains(ResourceItem.State.confirmSuccess))) { 66 | JDBCHelper.updateState(context, resource, (String) item.getStateMapping().get(ResourceItem.State.confirmSuccess), null); 67 | } 68 | } 69 | 70 | } 71 | 72 | class CancelAction implements TransactionAction { 73 | 74 | @Override 75 | public void action(TransactionContext context, TransactionResource resource, ResourceItem item) throws Throwable { 76 | try { 77 | if (item.hasCancel()) { 78 | item.cancelTx(context); 79 | } 80 | } catch (Throwable e) { 81 | if (resource.isUpdateState() && (item.getIgnoreUpdateState() == null || !item.getIgnoreUpdateState().contains(ResourceItem.State.cancelFailed))) { 82 | JDBCHelper.updateState(context, resource, (String) item.getStateMapping().get(ResourceItem.State.cancelFailed), e.getMessage()); 83 | } 84 | throw e; 85 | } 86 | if (resource.isUpdateState() && (item.getIgnoreUpdateState() == null || !item.getIgnoreUpdateState().contains(ResourceItem.State.cancelSuccess))) { 87 | JDBCHelper.updateState(context, resource, (String) item.getStateMapping().get(ResourceItem.State.cancelSuccess), null); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/state/TransactionState.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.state; 2 | 3 | import com.jd.tx.tcc.core.ResourceItemLinkedList; 4 | import com.jd.tx.tcc.core.TransactionContext; 5 | import com.jd.tx.tcc.core.TransactionResource; 6 | 7 | /** 8 | * @author Leon Guo 9 | * Creation Date: 2016/4/26 10 | */ 11 | public interface TransactionState { 12 | 13 | void handle(StateContext stateContext); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/state/TryState.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.state; 2 | 3 | import com.jd.tx.tcc.core.ResourceItem; 4 | import com.jd.tx.tcc.core.ResourceItemLinkedList; 5 | import com.jd.tx.tcc.core.exception.SOATxUnawareException; 6 | import com.jd.tx.tcc.core.exception.SOATxUnrecoverableException; 7 | 8 | /** 9 | * @author Leon Guo 10 | * Creation Date: 2016/4/26 11 | */ 12 | public class TryState implements TransactionState { 13 | 14 | public final static TryState instance = new TryState(); 15 | 16 | @Override 17 | public void handle(StateContext stateContext) { 18 | try { 19 | ResourceItemLinkedList linkedItem = stateContext.getItemLinkedList(); 20 | 21 | if (linkedItem.getItem().getStateMapping() == null || 22 | !linkedItem.getItem().getStateMapping().containsKey(ResourceItem.State.trySuccess) || 23 | !linkedItem.getItem().getStateMapping().get(ResourceItem.State.trySuccess) 24 | .equals( 25 | stateContext.getTransactionContext().getState() 26 | )) { 27 | TransactionAction.tryAction.action(stateContext.getTransactionContext(), stateContext.getResource(), linkedItem.getItem()); 28 | } 29 | stateContext.getTransactionContext().setState(null); 30 | 31 | while (linkedItem.hasNext()) { 32 | linkedItem = linkedItem.getNext(); 33 | stateContext.setItemLinkedList(linkedItem); 34 | TransactionAction.tryAction.action(stateContext.getTransactionContext(), stateContext.getResource(), linkedItem.getItem()); 35 | } 36 | stateContext.moveToHead(); 37 | stateContext.setState(ConfirmState.instance); 38 | stateContext.invokeState(); 39 | } catch (SOATxUnrecoverableException e) { 40 | //unrecoverable exception happened, cancel. 41 | if (stateContext.getItemLinkedList().hasPre()) { 42 | stateContext.setItemLinkedList(stateContext.getItemLinkedList().getPre()); 43 | stateContext.setState(CancelState.instance); 44 | stateContext.invokeState(); 45 | } 46 | //tell the client this transaction failed. 47 | throw e; 48 | } catch (Throwable e) { 49 | //unaware exception happened, shut down process and wait for retry. 50 | throw new SOATxUnawareException(e.getMessage(), e); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/sync/SyncTransactionRunner.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.sync; 2 | 3 | import com.jd.tx.tcc.core.*; 4 | import com.jd.tx.tcc.core.exception.SOATxUnawareException; 5 | import com.jd.tx.tcc.core.exception.SOATxUnrecoverableException; 6 | import com.jd.tx.tcc.core.state.*; 7 | import lombok.Getter; 8 | import lombok.NonNull; 9 | import lombok.Setter; 10 | import org.apache.commons.lang3.Validate; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * @author Leon Guo 16 | * Creation Date: 2016/2/16 17 | */ 18 | public class SyncTransactionRunner implements TransactionRunner { 19 | 20 | @Setter 21 | @Getter 22 | private TransactionManager transactionManager; 23 | 24 | @Override 25 | public void run(@NonNull TransactionContext context) { 26 | Validate.notNull(context.getId()); 27 | Validate.notNull(context.getKey()); 28 | Validate.notNull(context.getState()); 29 | Validate.notNull(transactionManager); 30 | 31 | TransactionResource resource = transactionManager.getResource(context.getKey()); 32 | ResourceItemLinkedList itemLinkedList = ResourceItemLinkedList.build(resource.getResourceItems()); 33 | while (itemLinkedList != null) { 34 | TransactionState txState = getState(context.getState(), itemLinkedList.getItem()); 35 | if (txState != null) { 36 | StateContext stateContext = new StateContext(context, resource, itemLinkedList, txState); 37 | stateContext.invokeState(); 38 | break; 39 | } 40 | 41 | if (itemLinkedList.hasNext()) { 42 | itemLinkedList = itemLinkedList.getNext(); 43 | } else { 44 | itemLinkedList = null; 45 | } 46 | } 47 | } 48 | 49 | private TransactionState getState(String contextState, ResourceItem item) { 50 | if (item.getStateMapping() == null || item.getStateMapping().isEmpty()) { 51 | return null; 52 | } 53 | if (contextState.equals(item.getStateMapping().get(ResourceItem.State.begin))) { 54 | return BeginState.instance; 55 | } 56 | if (contextState.equals(item.getStateMapping().get(ResourceItem.State.trySuccess)) 57 | || contextState.equals(item.getStateMapping().get(ResourceItem.State.tryFailed))) { 58 | return TryState.instance; 59 | } 60 | if (contextState.equals(item.getStateMapping().get(ResourceItem.State.confirmSuccess)) 61 | || contextState.equals(item.getStateMapping().get(ResourceItem.State.confirmFailed))) { 62 | return ConfirmState.instance; 63 | } 64 | if (contextState.equals(item.getStateMapping().get(ResourceItem.State.cancelSuccess)) 65 | || contextState.equals(item.getStateMapping().get(ResourceItem.State.cancelFailed))) { 66 | return CancelState.instance; 67 | } 68 | return null; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /tcctx-core/src/main/java/com/jd/tx/tcc/core/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.utils; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * @author Leon Guo 7 | * Creation Date: 2016/4/8 8 | */ 9 | public class Utils { 10 | 11 | public final static int DEFAULT_BUFFER_SIZE = 1024 * 4; 12 | 13 | public static byte[] readByteArrayFromResource(String resource) throws IOException { 14 | try (InputStream in =Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)) { 15 | if (in == null) { 16 | return null; 17 | } 18 | 19 | return readByteArray(in); 20 | } 21 | } 22 | 23 | public static String readFromResource(String resource) throws IOException { 24 | try (InputStream in = 25 | Thread.currentThread().getContextClassLoader().getResourceAsStream(resource) == null ? 26 | Utils.class.getResourceAsStream(resource) : Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)) { 27 | if (in == null) { 28 | return null; 29 | } 30 | 31 | String text = Utils.read(in); 32 | return text; 33 | } 34 | } 35 | 36 | public static String read(InputStream in) { 37 | InputStreamReader reader; 38 | try { 39 | reader = new InputStreamReader(in, "UTF-8"); 40 | } catch (UnsupportedEncodingException e) { 41 | throw new IllegalStateException(e.getMessage(), e); 42 | } 43 | return read(reader); 44 | } 45 | 46 | public static String read(Reader reader) { 47 | try { 48 | 49 | StringWriter writer = new StringWriter(); 50 | 51 | char[] buffer = new char[DEFAULT_BUFFER_SIZE]; 52 | int n; 53 | while (-1 != (n = reader.read(buffer))) { 54 | writer.write(buffer, 0, n); 55 | } 56 | 57 | return writer.toString(); 58 | } catch (IOException ex) { 59 | throw new IllegalStateException("read error", ex); 60 | } 61 | } 62 | 63 | public static byte[] readByteArray(InputStream input) throws IOException { 64 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 65 | copy(input, output); 66 | return output.toByteArray(); 67 | } 68 | 69 | public static long copy(InputStream input, OutputStream output) throws IOException { 70 | final int EOF = -1; 71 | 72 | byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 73 | 74 | long count = 0; 75 | int n = 0; 76 | while (EOF != (n = input.read(buffer))) { 77 | output.write(buffer, 0, n); 78 | count += n; 79 | } 80 | return count; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/TestMain.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import com.jd.tx.tcc.core.impl.TestSeqStateGenerator; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Suite; 6 | 7 | /** 8 | * @author Leon Guo 9 | * Creation Date: 2016/4/27 10 | */ 11 | 12 | @RunWith(Suite.class) 13 | @Suite.SuiteClasses({ 14 | TestSeqStateGenerator.class, 15 | TestResourceItemLinkedList.class, 16 | TestTransactionRunner.class 17 | }) 18 | public class TestMain { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/TestResourceItemLinkedList.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import com.jd.tx.tcc.core.test.mock.MockResourceItem; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @author Leon Guo 13 | * Creation Date: 2016/4/26 14 | */ 15 | public class TestResourceItemLinkedList { 16 | 17 | private List resourceItems; 18 | 19 | @Before 20 | public void setup() { 21 | resourceItems = new ArrayList<>(); 22 | resourceItems.add(MockResourceItem.buildMock()); 23 | resourceItems.add(MockResourceItem.buildMock()); 24 | resourceItems.add(MockResourceItem.buildMock()); 25 | resourceItems.add(MockResourceItem.buildMock()); 26 | } 27 | 28 | @Test 29 | public void testBuildLinkedList() { 30 | ResourceItemLinkedList resourceItemLinkedList = ResourceItemLinkedList.build(resourceItems); 31 | System.out.println(resourceItemLinkedList); 32 | Assert.assertEquals(resourceItems.get(0), resourceItemLinkedList.getItem()); 33 | Assert.assertNull(resourceItemLinkedList.getPre()); 34 | Assert.assertEquals(resourceItems.get(1), resourceItemLinkedList.getNext().getItem()); 35 | int i = 1; 36 | while (resourceItemLinkedList.hasNext()) { 37 | resourceItemLinkedList = resourceItemLinkedList.getNext(); 38 | System.out.println(resourceItemLinkedList); 39 | Assert.assertEquals(resourceItems.get(i), resourceItemLinkedList.getItem()); 40 | Assert.assertEquals(resourceItems.get(i - 1), resourceItemLinkedList.getPre().getItem()); 41 | if (i == resourceItems.size() - 1) { 42 | Assert.assertNull(resourceItemLinkedList.getNext()); 43 | } else { 44 | Assert.assertEquals(resourceItems.get(i + 1), resourceItemLinkedList.getNext().getItem()); 45 | } 46 | i++; 47 | } 48 | Assert.assertEquals(4, i); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/TestTransactionRunner.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core; 2 | 3 | import com.jd.tx.tcc.core.exception.SOATxUnawareException; 4 | import com.jd.tx.tcc.core.exception.SOATxUnrecoverableException; 5 | import com.jd.tx.tcc.core.impl.CommonTransactionContext; 6 | import com.jd.tx.tcc.core.impl.SeqStateGenerator; 7 | import com.jd.tx.tcc.core.sync.SyncTransactionRunner; 8 | import com.jd.tx.tcc.core.test.hsql.HsqlDatabase; 9 | import com.jd.tx.tcc.core.test.hsql.TestTableManager; 10 | import com.jd.tx.tcc.core.test.mock.MockResourceItem; 11 | import org.junit.*; 12 | import org.junit.rules.ExpectedException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.sql.SQLException; 17 | import java.text.SimpleDateFormat; 18 | import java.util.*; 19 | 20 | import static org.junit.Assert.fail; 21 | import static org.mockito.Matchers.any; 22 | import static org.mockito.Mockito.times; 23 | import static org.mockito.Mockito.verify; 24 | 25 | /** 26 | * @author Leon Guo 27 | * Creation Date: 2016/3/10 28 | */ 29 | public class TestTransactionRunner { 30 | 31 | private static final Logger LOG = LoggerFactory.getLogger(TestTransactionRunner.class); 32 | 33 | private TransactionRunner txRunner; 34 | 35 | private TransactionResource txRes; 36 | 37 | private TransactionManager txManager; 38 | 39 | @Rule 40 | public ExpectedException thrown = ExpectedException.none(); 41 | 42 | @BeforeClass 43 | public static void beforeClass() throws SQLException { 44 | TestTableManager.createTestTables(); 45 | } 46 | 47 | @AfterClass 48 | public static void afterClass() throws SQLException { 49 | TestTableManager.dropTestTables(); 50 | } 51 | 52 | @Before 53 | public void setup() { 54 | txRes = buildTxRes(); 55 | txManager = new TransactionManager(); 56 | Map resourceMap = new HashMap<>(); 57 | resourceMap.put("TEST_TX", txRes); 58 | 59 | TransactionManager txManager = new TransactionManager(); 60 | txManager.setResourcesMap(resourceMap); 61 | 62 | txRunner = new SyncTransactionRunner(); 63 | txRunner.setTransactionManager(txManager); 64 | } 65 | 66 | @Test 67 | public void testSimpleFlow() throws SQLException { 68 | LOG.info("begin test tx runner"); 69 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyCommit()); 70 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyCommit().noTry()); 71 | txRes.init(); 72 | 73 | CommonTransactionContext context = buildContext("999"); 74 | txRunner.run(context); 75 | 76 | verify(txRes.getResourceItems().get(0), times(1)).confirmTx(any(TransactionContext.class)); 77 | verify(txRes.getResourceItems().get(1), times(1)).confirmTx(any(TransactionContext.class)); 78 | 79 | verify(txRes.getResourceItems().get(0), times(0)).cancelTx(any(TransactionContext.class)); 80 | verify(txRes.getResourceItems().get(1), times(0)).cancelTx(any(TransactionContext.class)); 81 | 82 | verify(txRes.getResourceItems().get(0), times(1)).tryTx(any(TransactionContext.class)); 83 | verify(txRes.getResourceItems().get(1), times(0)).tryTx(any(TransactionContext.class)); 84 | 85 | String dbValue = TestTableManager.query("999"); 86 | LOG.info(dbValue); 87 | String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 88 | Assert.assertEquals("[999, " 89 | + txRes.getResourceItems().get(1).getStateMapping().get(ResourceItem.State.confirmSuccess) 90 | + ", unit test, " 91 | + today + "]", 92 | dbValue); 93 | } 94 | 95 | @Test 96 | public void testInterruptCommitFlow() throws SQLException { 97 | LOG.info("begin test tx runner"); 98 | txRes.getResourceItems().add(MockResourceItem.buildMock()); 99 | txRes.getResourceItems().add(MockResourceItem.buildMock().noTry().throwUnawareExWhenCommit()); 100 | txRes.getResourceItems().add(MockResourceItem.buildMock().noTry()); 101 | txRes.init(); 102 | 103 | CommonTransactionContext context = buildContext("998"); 104 | 105 | // thrown.expect(SOATxUnawareException.class); 106 | // thrown.expectMessage("unaware exception"); 107 | try { 108 | txRunner.run(context); 109 | fail("No SOATxUnawareException be thrown!"); 110 | } catch (SOATxUnawareException e) { 111 | LOG.info("got SOATxUnawareException successful!"); 112 | } 113 | 114 | verify(txRes.getResourceItems().get(0), times(1)).confirmTx(any(TransactionContext.class)); 115 | verify(txRes.getResourceItems().get(1), times(1)).confirmTx(any(TransactionContext.class)); 116 | verify(txRes.getResourceItems().get(2), times(0)).confirmTx(any(TransactionContext.class)); 117 | 118 | verify(txRes.getResourceItems().get(0), times(0)).cancelTx(any(TransactionContext.class)); 119 | verify(txRes.getResourceItems().get(1), times(0)).cancelTx(any(TransactionContext.class)); 120 | verify(txRes.getResourceItems().get(2), times(0)).cancelTx(any(TransactionContext.class)); 121 | 122 | verify(txRes.getResourceItems().get(0), times(1)).tryTx(any(TransactionContext.class)); 123 | verify(txRes.getResourceItems().get(1), times(0)).tryTx(any(TransactionContext.class)); 124 | verify(txRes.getResourceItems().get(2), times(0)).tryTx(any(TransactionContext.class)); 125 | 126 | String dbValue = TestTableManager.query("998"); 127 | LOG.info(dbValue); 128 | String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 129 | //the state is the success state of last item, waiting for retrying. 130 | Assert.assertEquals("[998, " 131 | + txRes.getResourceItems().get(0).getStateMapping().get(ResourceItem.State.confirmSuccess) 132 | + ", unit test, " 133 | + today + "]", 134 | dbValue); 135 | } 136 | 137 | @Test 138 | public void testCancelCommitFlow() throws SQLException { 139 | LOG.info("begin test tx runner"); 140 | txRes.getResourceItems().add(MockResourceItem.buildMock()); 141 | txRes.getResourceItems().add(MockResourceItem.buildMock().noTry().throwUnrecoverableExWhenCommit()); 142 | txRes.getResourceItems().add(MockResourceItem.buildMock().noTry().noCancel()); 143 | txRes.init(); 144 | 145 | CommonTransactionContext context = buildContext("997"); 146 | 147 | try { 148 | txRunner.run(context); 149 | fail("No SOATxUnrecoverableException be thrown!"); 150 | } catch (SOATxUnrecoverableException e) { 151 | LOG.info("got SOATxUnrecoverableException successful!"); 152 | } 153 | 154 | verify(txRes.getResourceItems().get(0), times(1)).confirmTx(any(TransactionContext.class)); 155 | verify(txRes.getResourceItems().get(1), times(1)).confirmTx(any(TransactionContext.class)); 156 | verify(txRes.getResourceItems().get(2), times(0)).confirmTx(any(TransactionContext.class)); 157 | 158 | verify(txRes.getResourceItems().get(0), times(1)).cancelTx(any(TransactionContext.class)); 159 | verify(txRes.getResourceItems().get(1), times(1)).cancelTx(any(TransactionContext.class)); 160 | verify(txRes.getResourceItems().get(2), times(0)).cancelTx(any(TransactionContext.class)); 161 | 162 | verify(txRes.getResourceItems().get(0), times(1)).tryTx(any(TransactionContext.class)); 163 | verify(txRes.getResourceItems().get(1), times(0)).tryTx(any(TransactionContext.class)); 164 | verify(txRes.getResourceItems().get(2), times(0)).tryTx(any(TransactionContext.class)); 165 | 166 | String dbValue = TestTableManager.query("997"); 167 | LOG.info(dbValue); 168 | String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 169 | //the state is the canceled successful of the first item, means all items have been canceled. 170 | Assert.assertEquals("[997, " 171 | + txRes.getResourceItems().get(0).getStateMapping().get(ResourceItem.State.cancelSuccess) 172 | + ", unrecoverable exception, " 173 | + today + "]", 174 | dbValue); 175 | } 176 | 177 | @Test 178 | public void testInterruptTryFlow() throws SQLException { 179 | LOG.info("begin test tx runner"); 180 | txRes.getResourceItems().add(MockResourceItem.buildMock()); 181 | txRes.getResourceItems().add(MockResourceItem.buildMock().throwUnawareExWhenTry()); 182 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyTry()); 183 | txRes.init(); 184 | 185 | CommonTransactionContext context = buildContext("996"); 186 | 187 | try { 188 | txRunner.run(context); 189 | fail("No SOATxUnawareException be thrown!"); 190 | } catch (SOATxUnawareException e) { 191 | LOG.info("got SOATxUnawareException successful!"); 192 | } 193 | 194 | verify(txRes.getResourceItems().get(0), times(0)).confirmTx(any(TransactionContext.class)); 195 | verify(txRes.getResourceItems().get(1), times(0)).confirmTx(any(TransactionContext.class)); 196 | verify(txRes.getResourceItems().get(2), times(0)).confirmTx(any(TransactionContext.class)); 197 | 198 | verify(txRes.getResourceItems().get(0), times(0)).cancelTx(any(TransactionContext.class)); 199 | verify(txRes.getResourceItems().get(1), times(0)).cancelTx(any(TransactionContext.class)); 200 | verify(txRes.getResourceItems().get(2), times(0)).cancelTx(any(TransactionContext.class)); 201 | 202 | verify(txRes.getResourceItems().get(0), times(1)).tryTx(any(TransactionContext.class)); 203 | verify(txRes.getResourceItems().get(1), times(1)).tryTx(any(TransactionContext.class)); 204 | verify(txRes.getResourceItems().get(2), times(0)).tryTx(any(TransactionContext.class)); 205 | 206 | String dbValue = TestTableManager.query("996"); 207 | LOG.info(dbValue); 208 | String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 209 | Assert.assertEquals("[996, " 210 | + txRes.getResourceItems().get(0).getStateMapping().get(ResourceItem.State.trySuccess) 211 | + ", unit test, " 212 | + today + "]", 213 | dbValue); 214 | } 215 | 216 | @Test 217 | public void testCancelTryFlow() throws SQLException { 218 | LOG.info("begin test tx runner"); 219 | txRes.getResourceItems().add(MockResourceItem.buildMock()); 220 | txRes.getResourceItems().add(MockResourceItem.buildMock().throwUnrecoverableExWhenTry()); 221 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyTry()); 222 | txRes.init(); 223 | 224 | CommonTransactionContext context = buildContext("995"); 225 | 226 | try { 227 | txRunner.run(context); 228 | fail("No SOATxUnrecoverableException be thrown!"); 229 | } catch (SOATxUnrecoverableException e) { 230 | LOG.info("got SOATxUnrecoverableException successful!"); 231 | } 232 | 233 | verify(txRes.getResourceItems().get(0), times(0)).confirmTx(any(TransactionContext.class)); 234 | verify(txRes.getResourceItems().get(1), times(0)).confirmTx(any(TransactionContext.class)); 235 | verify(txRes.getResourceItems().get(2), times(0)).confirmTx(any(TransactionContext.class)); 236 | 237 | verify(txRes.getResourceItems().get(0), times(1)).cancelTx(any(TransactionContext.class)); 238 | verify(txRes.getResourceItems().get(1), times(0)).cancelTx(any(TransactionContext.class)); 239 | verify(txRes.getResourceItems().get(2), times(0)).cancelTx(any(TransactionContext.class)); 240 | 241 | verify(txRes.getResourceItems().get(0), times(1)).tryTx(any(TransactionContext.class)); 242 | verify(txRes.getResourceItems().get(1), times(1)).tryTx(any(TransactionContext.class)); 243 | verify(txRes.getResourceItems().get(2), times(0)).tryTx(any(TransactionContext.class)); 244 | 245 | String dbValue = TestTableManager.query("995"); 246 | LOG.info(dbValue); 247 | String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 248 | Assert.assertEquals("[995, " 249 | + txRes.getResourceItems().get(0).getStateMapping().get(ResourceItem.State.cancelSuccess) 250 | + ", unrecoverable exception, " 251 | + today + "]", 252 | dbValue); 253 | } 254 | 255 | @Test 256 | public void testInterruptCancelFlow() throws SQLException { 257 | LOG.info("begin test tx runner"); 258 | txRes.getResourceItems().add(MockResourceItem.buildMock()); 259 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyTry().throwUnawareExWhenCancel()); 260 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyTry().throwUnrecoverableExWhenCommit()); 261 | txRes.init(); 262 | 263 | CommonTransactionContext context = buildContext("994"); 264 | 265 | try { 266 | txRunner.run(context); 267 | fail("No SOATxUnawareException be thrown!"); 268 | } catch (SOATxUnawareException e) { 269 | LOG.info("got SOATxUnawareException successful!"); 270 | } 271 | 272 | verify(txRes.getResourceItems().get(0), times(1)).confirmTx(any(TransactionContext.class)); 273 | verify(txRes.getResourceItems().get(1), times(1)).confirmTx(any(TransactionContext.class)); 274 | verify(txRes.getResourceItems().get(2), times(1)).confirmTx(any(TransactionContext.class)); 275 | 276 | verify(txRes.getResourceItems().get(0), times(0)).cancelTx(any(TransactionContext.class)); 277 | verify(txRes.getResourceItems().get(1), times(1)).cancelTx(any(TransactionContext.class)); 278 | verify(txRes.getResourceItems().get(2), times(1)).cancelTx(any(TransactionContext.class)); 279 | 280 | verify(txRes.getResourceItems().get(0), times(1)).tryTx(any(TransactionContext.class)); 281 | verify(txRes.getResourceItems().get(1), times(1)).tryTx(any(TransactionContext.class)); 282 | verify(txRes.getResourceItems().get(2), times(1)).tryTx(any(TransactionContext.class)); 283 | 284 | String dbValue = TestTableManager.query("994"); 285 | LOG.info(dbValue); 286 | String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 287 | Assert.assertEquals("[994, " 288 | + txRes.getResourceItems().get(1).getStateMapping().get(ResourceItem.State.cancelFailed) 289 | + ", unaware exception, " 290 | + today + "]", 291 | dbValue); 292 | } 293 | 294 | private CommonTransactionContext buildContext(String id) { 295 | CommonTransactionContext context = new CommonTransactionContext(); 296 | context.setKey("TEST_TX"); 297 | context.setDataSource(HsqlDatabase.getInstance().getDataSource()); 298 | context.setId(id); 299 | context.setState(txRes.getBeginningState()); 300 | return context; 301 | } 302 | 303 | private TransactionResource buildTxRes() { 304 | TransactionResource txRes = new TransactionResource(); 305 | txRes.setStateGenerator(new SeqStateGenerator()); 306 | txRes.setTable("test_tcctx"); 307 | txRes.setStateCol("status"); 308 | txRes.setIdCol("id"); 309 | txRes.setHandleTimeCol("last_handle_time"); 310 | txRes.setMsgCol("process_msg"); 311 | txRes.setMsgMaxLength(500); 312 | List resourceItems = new ArrayList<>(); 313 | txRes.setResourceItems(resourceItems); 314 | return txRes; 315 | } 316 | 317 | } 318 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/impl/TestJDBCHelper.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.impl; 2 | 3 | import com.jd.tx.tcc.core.ResourceItem; 4 | import com.jd.tx.tcc.core.TransactionManager; 5 | import com.jd.tx.tcc.core.TransactionResource; 6 | import com.jd.tx.tcc.core.TransactionRunner; 7 | import com.jd.tx.tcc.core.entity.TransactionEntity; 8 | import com.jd.tx.tcc.core.query.TransactionQuery; 9 | import com.jd.tx.tcc.core.sync.SyncTransactionRunner; 10 | import com.jd.tx.tcc.core.test.hsql.HsqlDatabase; 11 | import com.jd.tx.tcc.core.test.hsql.TestTableManager; 12 | import com.jd.tx.tcc.core.test.mock.MockResourceItem; 13 | import org.junit.AfterClass; 14 | import org.junit.Before; 15 | import org.junit.BeforeClass; 16 | import org.junit.Test; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.sql.SQLException; 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * @author Leon Guo 28 | * Creation Date: 2016/4/27 29 | */ 30 | public class TestJDBCHelper { 31 | 32 | private static final Logger LOG = LoggerFactory.getLogger(TestJDBCHelper.class); 33 | 34 | private TransactionRunner txRunner; 35 | 36 | private TransactionResource txRes; 37 | 38 | private TransactionManager txManager; 39 | 40 | @BeforeClass 41 | public static void beforeClass() throws SQLException { 42 | TestTableManager.createTestTables(); 43 | } 44 | 45 | @AfterClass 46 | public static void afterClass() throws SQLException { 47 | TestTableManager.dropTestTables(); 48 | } 49 | 50 | @Before 51 | public void setup() { 52 | txRes = buildTxRes(); 53 | txManager = new TransactionManager(); 54 | Map resourceMap = new HashMap<>(); 55 | resourceMap.put("TEST_TX", txRes); 56 | 57 | TransactionManager txManager = new TransactionManager(); 58 | txManager.setResourcesMap(resourceMap); 59 | 60 | txRunner = new SyncTransactionRunner(); 61 | txRunner.setTransactionManager(txManager); 62 | } 63 | 64 | @Test 65 | public void testFindTimeout() { 66 | txRes.getResourceItems().add(MockResourceItem.buildMock()); 67 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyTry().throwUnawareExWhenCancel()); 68 | txRes.getResourceItems().add(MockResourceItem.buildMock().emptyTry().throwUnrecoverableExWhenCommit()); 69 | txRes.init(); 70 | 71 | TransactionQuery query = new TransactionQuery(buildContext(), txRes) 72 | .setMinutesBefore(1) 73 | .setQueryRows(200); 74 | query.setExcludeStates(buildExcludeStates(txRes.getResourceItems())); 75 | 76 | List timeoutItems = JDBCHelper.findTimeoutItems(query); 77 | for (TransactionEntity entity : timeoutItems) { 78 | LOG.info(entity.toString()); 79 | } 80 | } 81 | 82 | private CommonTransactionContext buildContext() { 83 | CommonTransactionContext context = new CommonTransactionContext(); 84 | context.setKey("TEST_TX"); 85 | context.setDataSource(HsqlDatabase.getInstance().getDataSource()); 86 | return context; 87 | } 88 | 89 | private TransactionResource buildTxRes() { 90 | TransactionResource txRes = new TransactionResource(); 91 | txRes.setStateGenerator(new SeqStateGenerator()); 92 | txRes.setTable("test_tcctx"); 93 | txRes.setStateCol("status"); 94 | txRes.setIdCol("id"); 95 | txRes.setHandleTimeCol("last_handle_time"); 96 | txRes.setMsgCol("process_msg"); 97 | txRes.setMsgMaxLength(500); 98 | List resourceItems = new ArrayList<>(); 99 | txRes.setResourceItems(resourceItems); 100 | return txRes; 101 | } 102 | 103 | private List buildExcludeStates(List resourceItems) { 104 | List excludeStates = new ArrayList<>(); 105 | if (resourceItems.get(resourceItems.size() - 1).getStateMapping().get(ResourceItem.State.confirmSuccess) != null) { 106 | excludeStates.add((String) resourceItems.get(resourceItems.size() - 1).getStateMapping().get(ResourceItem.State.confirmSuccess)); 107 | } 108 | if (resourceItems.get(0).getStateMapping().get(ResourceItem.State.cancelSuccess) != null) { 109 | excludeStates.add((String) resourceItems.get(0).getStateMapping().get(ResourceItem.State.cancelSuccess)); 110 | } 111 | return excludeStates; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/impl/TestSeqStateGenerator.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.impl; 2 | 3 | import com.google.common.collect.BiMap; 4 | import com.jd.tx.tcc.core.ResourceItem; 5 | import com.jd.tx.tcc.core.TransactionResource; 6 | import com.jd.tx.tcc.core.test.mock.MockResourceItem; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import static org.mockito.Matchers.any; 17 | import static org.mockito.Mockito.*; 18 | 19 | /** 20 | * @author Leon Guo 21 | * Creation Date: 2016/3/10 22 | */ 23 | public class TestSeqStateGenerator { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(TestSeqStateGenerator.class); 26 | 27 | private SeqStateGenerator generator; 28 | 29 | private List resourceItems; 30 | 31 | @Before 32 | public void setUp() { 33 | generator = new SeqStateGenerator(); 34 | resourceItems = new ArrayList<>(); 35 | } 36 | 37 | @Test 38 | public void testStateGenerator() { 39 | MockResourceItem item1 = MockResourceItem.buildMock(); 40 | MockResourceItem item2 = MockResourceItem.buildMock(); 41 | MockResourceItem item3 = MockResourceItem.buildMock(); 42 | 43 | resourceItems.add(item1); 44 | resourceItems.add(item2); 45 | resourceItems.add(item3); 46 | 47 | generator.generatorStates(resourceItems); 48 | 49 | LOG.info(item1.getStateMapping().values().toString()); 50 | Assert.assertEquals("[100, 111, 110, 121, 120, 131, 130]", item1.getStateMapping().values().toString()); 51 | LOG.info(item2.getStateMapping().values().toString()); 52 | Assert.assertEquals("[200, 211, 210, 221, 220, 231, 230]", item2.getStateMapping().values().toString()); 53 | LOG.info(item3.getStateMapping().values().toString()); 54 | Assert.assertEquals("[300, 311, 310, 321, 320, 331, 330]", item3.getStateMapping().values().toString()); 55 | } 56 | 57 | @Test 58 | public void testInputStateGenerator() { 59 | MockResourceItem item1 = getMockResourceItem(1); 60 | MockResourceItem item2 = getMockResourceItem(2); 61 | MockResourceItem item3 = getMockResourceItem(3); 62 | 63 | resourceItems.add(item1); 64 | resourceItems.add(item2); 65 | resourceItems.add(item3); 66 | 67 | generator.generatorStates(resourceItems); 68 | 69 | LOG.info(item1.getStateMapping().values().toString()); 70 | Assert.assertEquals("[100, 111, 110, 121, 120, 131, 130]", item1.getStateMapping().values().toString()); 71 | LOG.info(item2.getStateMapping().values().toString()); 72 | Assert.assertEquals("[200, 211, 210, 221, 220, 231, 230]", item2.getStateMapping().values().toString()); 73 | LOG.info(item3.getStateMapping().values().toString()); 74 | Assert.assertEquals("[300, 311, 310, 321, 320, 331, 330]", item3.getStateMapping().values().toString()); 75 | } 76 | 77 | @Test 78 | public void testStateGeneratorWithTxRes() { 79 | MockResourceItem item1 = MockResourceItem.buildMock(); 80 | MockResourceItem item2 = MockResourceItem.buildMock(); 81 | MockResourceItem item3 = MockResourceItem.buildMock(); 82 | 83 | resourceItems.add(item1); 84 | resourceItems.add(item2); 85 | resourceItems.add(item3); 86 | 87 | TransactionResource tr = new TransactionResource(); 88 | tr.setStateGenerator(generator); 89 | tr.setResourceItems(resourceItems); 90 | tr.init(); 91 | Assert.assertEquals("100", tr.getBeginningState()); 92 | } 93 | 94 | private MockResourceItem getMockResourceItem(int value) { 95 | MockResourceItem item1 = mock(MockResourceItem.class); 96 | when(item1.getStateIndex()).thenReturn(value); 97 | doCallRealMethod().when(item1).setStateMapping(any(BiMap.class)); 98 | when(item1.getStateMapping()).thenCallRealMethod(); 99 | return item1; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/test/hsql/HsqlDatabase.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.test.hsql; 2 | 3 | import lombok.Getter; 4 | import org.hsqldb.jdbc.JDBCDataSource; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.sql.Connection; 9 | import java.sql.SQLException; 10 | import java.sql.Statement; 11 | 12 | /** 13 | * @author Leon Guo 14 | * Creation Date: 2016/4/15 15 | */ 16 | public class HsqlDatabase { 17 | 18 | private static final String CONNECTION_STRING = "jdbc:hsqldb:mem:testdb;shutdown=false"; 19 | private static final String USER_NAME = "SA"; 20 | private static final String PASSWORD = ""; 21 | private static final Logger LOG = LoggerFactory.getLogger(HsqlDatabase.class); 22 | 23 | @Getter 24 | private JDBCDataSource dataSource; 25 | 26 | private HsqlDatabase() {} 27 | 28 | private static HsqlDatabase database = new HsqlDatabase(); 29 | 30 | static { 31 | try { 32 | database.init(); 33 | } catch (SQLException e) { 34 | LOG.error(e.getMessage(), e); 35 | } 36 | } 37 | 38 | public static HsqlDatabase getInstance() { 39 | return database; 40 | } 41 | 42 | public void init() throws SQLException { 43 | dataSource = new JDBCDataSource(); 44 | dataSource.setUrl(CONNECTION_STRING); 45 | dataSource.setUser(USER_NAME); 46 | dataSource.setPassword(PASSWORD); 47 | } 48 | 49 | public void executeSql(String sql) throws SQLException { 50 | try (Connection conn = HsqlDatabase.database.getConnection(); Statement stat = conn.createStatement();) { 51 | stat.execute(sql); 52 | conn.commit(); 53 | } catch (SQLException e) { 54 | LOG.error(e.getMessage(), e); 55 | throw e; 56 | } 57 | } 58 | 59 | public void close() throws SQLException { 60 | try { 61 | if (dataSource != null && dataSource.getConnection() != null) { 62 | dataSource.getConnection().close(); 63 | } 64 | } catch (SQLException e) { 65 | LOG.error(e.getMessage(), e); 66 | throw e; 67 | } 68 | } 69 | 70 | public Connection getConnection() throws SQLException { 71 | return dataSource.getConnection(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/test/hsql/TestTableManager.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.test.hsql; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.sql.Connection; 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | import java.sql.Statement; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * @author Leon Guo 15 | * Creation Date: 2016/4/26 16 | */ 17 | public class TestTableManager { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(TestTableManager.class); 20 | 21 | private static final String CREATE_TBL_SQL = "CREATE TABLE test_tcctx (\n" + 22 | " id VARCHAR(32) NOT NULL,\n" + 23 | " status INTEGER NOT NULL,\n" + 24 | " process_msg VARCHAR(500),\n" + 25 | " last_handle_time TIMESTAMP NOT NULL,\n" + 26 | " PRIMARY KEY (id))"; 27 | 28 | private static final String DROP_TBL_SQL = "DROP TABLE test_tcctx"; 29 | 30 | public static void createTestTables() throws SQLException { 31 | HsqlDatabase.getInstance().executeSql(CREATE_TBL_SQL); 32 | } 33 | 34 | public static void dropTestTables() throws SQLException { 35 | HsqlDatabase.getInstance().executeSql(DROP_TBL_SQL); 36 | } 37 | 38 | public static String query(String id) throws SQLException { 39 | List list = new ArrayList<>(); 40 | try (Connection conn = HsqlDatabase.getInstance().getConnection(); Statement stat = conn.createStatement(); ) { 41 | String sql = "select * from test_tcctx where id = '" + id + "'"; 42 | LOG.info("query sql: " + sql); 43 | ResultSet resultSet = stat.executeQuery(sql); 44 | while (resultSet.next()) { 45 | list.add(resultSet.getString(1)); 46 | list.add(resultSet.getString(2)); 47 | list.add(resultSet.getString(3)); 48 | list.add(resultSet.getDate(4).toString()); 49 | } 50 | } 51 | return list.toString(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /tcctx-core/src/test/java/com/jd/tx/tcc/core/test/mock/MockResourceItem.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.core.test.mock; 2 | 3 | import com.google.common.collect.BiMap; 4 | import com.jd.tx.tcc.core.ResourceItem; 5 | import com.jd.tx.tcc.core.TransactionContext; 6 | import com.jd.tx.tcc.core.exception.SOATxUnawareException; 7 | import com.jd.tx.tcc.core.exception.SOATxUnrecoverableException; 8 | import com.jd.tx.tcc.core.test.hsql.HsqlDatabase; 9 | import org.mockito.invocation.InvocationOnMock; 10 | import org.mockito.stubbing.Answer; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.sql.SQLException; 15 | import java.util.List; 16 | 17 | import static org.mockito.Matchers.any; 18 | import static org.mockito.Mockito.*; 19 | 20 | /** 21 | * @author Leon Guo 22 | * Creation Date: 2016/4/25 23 | */ 24 | public class MockResourceItem implements ResourceItem { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(MockResourceItem.class); 27 | 28 | private BiMap stateMapping; 29 | 30 | private MockResourceItem() {} 31 | 32 | @Override 33 | public boolean hasTry() { 34 | return true; 35 | } 36 | 37 | @Override 38 | public boolean hasCancel() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public void tryTx(TransactionContext context) { 44 | String insertSql = "insert into test_tcctx (id, status, process_msg, last_handle_time) values ('" + 45 | context.getId() +"', '" + 46 | context.getState() + "', " + 47 | "'unit test', " + 48 | "now()" + 49 | " )"; 50 | try { 51 | LOG.info("insert sql: " + insertSql); 52 | HsqlDatabase.getInstance().executeSql(insertSql); 53 | } catch (SQLException e) { 54 | LOG.error(e.getMessage(), e); 55 | throw new SOATxUnrecoverableException(e); 56 | } 57 | } 58 | 59 | @Override 60 | public void confirmTx(TransactionContext context) { 61 | LOG.info("invoke confirm"); 62 | } 63 | 64 | @Override 65 | public void cancelTx(TransactionContext context) { 66 | LOG.info("invoke cancelTx"); 67 | } 68 | 69 | @Override 70 | public List getIgnoreUpdateState() { 71 | return null; 72 | } 73 | 74 | @Override 75 | public BiMap getStateMapping() { 76 | return stateMapping; 77 | } 78 | 79 | @Override 80 | public Integer getStateIndex() { 81 | return null; 82 | } 83 | 84 | @Override 85 | public void setStateMapping(BiMap stateMapping) { 86 | this.stateMapping = stateMapping; 87 | } 88 | 89 | public static MockResourceItem buildMock() { 90 | MockResourceItem item = new MockResourceItem(); 91 | MockResourceItem spyItem = spy(item); 92 | return spyItem; 93 | } 94 | 95 | public MockResourceItem emptyTry() { 96 | doAnswer(new Answer() { 97 | public Void answer(InvocationOnMock invocation) { 98 | Object[] args = invocation.getArguments(); 99 | LOG.info("called try with arguments: " + args); 100 | return null; 101 | } 102 | }).when(this).tryTx(any(TransactionContext.class)); 103 | return this; 104 | } 105 | 106 | public MockResourceItem emptyCommit() { 107 | doAnswer(new Answer() { 108 | public Void answer(InvocationOnMock invocation) { 109 | Object[] args = invocation.getArguments(); 110 | System.out.println("called normal item with arguments: " + args); 111 | return null; 112 | } 113 | }).when(this).confirmTx(any(TransactionContext.class)); 114 | return this; 115 | } 116 | 117 | public MockResourceItem noTry() { 118 | when(this.hasTry()).thenReturn(false); 119 | return this; 120 | } 121 | 122 | public MockResourceItem insertInTry() { 123 | doAnswer(new Answer() { 124 | public Void answer(InvocationOnMock invocation) { 125 | Object[] args = invocation.getArguments(); 126 | TransactionContext context = (TransactionContext) args[0]; 127 | String insertSql = "insert into test_tcctx (id, status, process_msg, last_handle_time) values ('" + 128 | context.getId() +"', '" + 129 | context.getState() + "', " + 130 | "'unit test', " + 131 | "now()" + 132 | " )"; 133 | try { 134 | LOG.info("insert sql: " + insertSql); 135 | HsqlDatabase.getInstance().executeSql(insertSql); 136 | } catch (Throwable e) { 137 | LOG.error(e.getMessage(), e); 138 | throw new SOATxUnawareException(e); 139 | } 140 | return null; 141 | } 142 | }).when(this).confirmTx(any(TransactionContext.class)); 143 | return this; 144 | } 145 | 146 | public MockResourceItem noCancel() { 147 | when(this.hasCancel()).thenReturn(false); 148 | return this; 149 | } 150 | 151 | public MockResourceItem throwUnawareExWhenCommit() { 152 | doThrow(new SOATxUnawareException("unaware exception")) 153 | .when(this).confirmTx(any(TransactionContext.class)); 154 | return this; 155 | } 156 | 157 | public MockResourceItem throwUnrecoverableExWhenCommit() { 158 | doThrow(new SOATxUnrecoverableException("unrecoverable exception")) 159 | .when(this).confirmTx(any(TransactionContext.class)); 160 | return this; 161 | } 162 | 163 | public MockResourceItem throwUnawareExWhenTry() { 164 | doThrow(new SOATxUnawareException("unaware exception")) 165 | .when(this).tryTx(any(TransactionContext.class)); 166 | return this; 167 | } 168 | 169 | public MockResourceItem throwUnrecoverableExWhenTry() { 170 | doThrow(new SOATxUnrecoverableException("unrecoverable exception")) 171 | .when(this).tryTx(any(TransactionContext.class)); 172 | return this; 173 | } 174 | 175 | public MockResourceItem throwUnawareExWhenCancel() { 176 | doThrow(new SOATxUnawareException("unaware exception")) 177 | .when(this).cancelTx(any(TransactionContext.class)); 178 | return this; 179 | } 180 | 181 | public MockResourceItem throwUnrecoverableExWhenCancel() { 182 | doThrow(new SOATxUnrecoverableException("unrecoverable exception")) 183 | .when(this).cancelTx(any(TransactionContext.class)); 184 | return this; 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /tcctx-core/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ${log.context.name} 9 | 10 | 11 | 12 | ${log.pattern} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tcctx-job/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | tcc 7 | com.jd.tx 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | tcctx-job 13 | 14 | 15 | 16 | com.jd.tx 17 | tcctx-core 18 | 1.0-SNAPSHOT 19 | 20 | 21 | com.dangdang 22 | elastic-job-core 23 | 1.0.4-SNAPSHOT 24 | 25 | 26 | com.dangdang 27 | elastic-job-spring 28 | 1.0.4-SNAPSHOT 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | com.alibaba 42 | fastjson 43 | 1.1.38 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-source-plugin 52 | ${maven-source-plugin.version} 53 | 54 | 55 | attach-sources 56 | verify 57 | 58 | jar-no-fork 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /tcctx-job/src/main/java/com/jd/tx/tcc/job/SyncJobRetryScheduler.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.job; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.dangdang.ddframe.job.api.JobExecutionMultipleShardingContext; 6 | import com.dangdang.ddframe.job.plugin.job.type.dataflow.AbstractBatchThroughputDataFlowElasticJob; 7 | import com.jd.tx.tcc.core.ResourceItem; 8 | import com.jd.tx.tcc.core.TransactionManager; 9 | import com.jd.tx.tcc.core.TransactionResource; 10 | import com.jd.tx.tcc.core.TransactionRunner; 11 | import com.jd.tx.tcc.core.entity.TransactionEntity; 12 | import com.jd.tx.tcc.core.impl.CommonTransactionContext; 13 | import com.jd.tx.tcc.core.query.TransactionQuery; 14 | import org.apache.commons.collections.CollectionUtils; 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.apache.commons.logging.Log; 17 | import org.apache.commons.logging.LogFactory; 18 | import org.quartz.JobExecutionException; 19 | import org.springframework.util.Assert; 20 | 21 | import javax.sql.DataSource; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * @author Leon Guo 28 | * Creation Date: 2016/2/26 29 | */ 30 | public class SyncJobRetryScheduler extends AbstractBatchThroughputDataFlowElasticJob { 31 | 32 | private static final Log log = LogFactory.getLog(SyncJobRetryScheduler.class); 33 | 34 | private static final int DEFAULT_TIMEOUT_MIN = 2; 35 | 36 | private TransactionRunner transactionRunner; 37 | 38 | private Map dataSourceMap; 39 | 40 | private String dbPrefix; 41 | 42 | private String lastId; 43 | 44 | private String key; 45 | 46 | private DataSource dataSource; 47 | 48 | private List includeStates; 49 | 50 | private List excludeStates; 51 | 52 | private int timeoutMins = DEFAULT_TIMEOUT_MIN; 53 | 54 | public SyncJobRetryScheduler() { 55 | } 56 | 57 | private DataSource getDataSource(JSONObject jsonObject) { 58 | if (dataSource != null) { 59 | return dataSource; 60 | } 61 | 62 | String dataSourceId = jsonObject.getString("dataSource"); 63 | dataSourceId = dataSourceId == null? "" : dataSourceId; 64 | 65 | String dataSourceKey = StringUtils.isBlank(dbPrefix) ? dataSourceId : dbPrefix + dataSourceId; 66 | return dataSourceMap.get(dataSourceKey); 67 | } 68 | 69 | private String getKey(JSONObject jsonObject) { 70 | String key = jsonObject.getString("key"); 71 | Assert.notNull(key); 72 | return key; 73 | } 74 | 75 | @Override 76 | public List fetchData(JobExecutionMultipleShardingContext shardingContext) { 77 | Assert.notNull(shardingContext); 78 | Assert.notNull(shardingContext.getJobParameter()); 79 | 80 | String jobParameter = shardingContext.getJobParameter(); 81 | JSONObject jsonObject = (JSONObject) JSON.parse(jobParameter); 82 | key = getKey(jsonObject); 83 | dataSource = getDataSource(jsonObject); 84 | 85 | TransactionManager transactionManager = transactionRunner.getTransactionManager(); 86 | TransactionResource resource = transactionManager.getResource(key); 87 | 88 | CommonTransactionContext context = new CommonTransactionContext(); 89 | context.setKey(key); 90 | context.setDataSource(dataSource); 91 | 92 | TransactionQuery query = new TransactionQuery(context, resource) 93 | .setSharding(shardingContext.getShardingTotalCount(), shardingContext.getShardingItems()) 94 | .setMinutesBefore(timeoutMins) 95 | .setQueryRows(200); 96 | if (StringUtils.isNotBlank(lastId)) { 97 | query.setLastId(lastId); 98 | } 99 | if (CollectionUtils.isNotEmpty(includeStates)) { 100 | query.setIncludeStates(includeStates); 101 | } else if (CollectionUtils.isNotEmpty(excludeStates)) { 102 | query.setExcludeStates(excludeStates); 103 | } else { 104 | query.setExcludeStates(buildExcludeStates(resource.getResourceItems())); 105 | } 106 | List timeoutItems = query.query(); 107 | 108 | if (CollectionUtils.isNotEmpty(timeoutItems)) { 109 | lastId = timeoutItems.get(timeoutItems.size() - 1).getId(); 110 | } else { 111 | //If no more data, set the lastId to null, make it load data from the very beginning in the next schedule time. 112 | lastId = null; 113 | } 114 | 115 | return timeoutItems; 116 | } 117 | 118 | private List buildExcludeStates(List resourceItems) { 119 | List excludeStates = new ArrayList<>(); 120 | if (resourceItems.get(resourceItems.size() - 1).getStateMapping().get(ResourceItem.State.confirmSuccess) != null) { 121 | excludeStates.add((String) resourceItems.get(resourceItems.size() - 1).getStateMapping().get(ResourceItem.State.confirmSuccess)); 122 | } 123 | if (resourceItems.get(0).getStateMapping().get(ResourceItem.State.cancelSuccess) != null) { 124 | excludeStates.add((String) resourceItems.get(0).getStateMapping().get(ResourceItem.State.cancelSuccess)); 125 | } 126 | return excludeStates; 127 | } 128 | 129 | @Override 130 | public int processData(JobExecutionMultipleShardingContext shardingContext, List data) { 131 | if (CollectionUtils.isEmpty(data)) { 132 | return 0; 133 | } 134 | 135 | // String jobParameter = shardingContext.getJobParameter(); 136 | // JSONObject jsonObject = (JSONObject) JSON.parse(jobParameter); 137 | // DataSource dataSource = getDataSource(jsonObject); 138 | // String key = getKey(jsonObject); 139 | 140 | int successNum = 0; 141 | for (TransactionEntity entity : data) { 142 | CommonTransactionContext txContext = new CommonTransactionContext(); 143 | 144 | txContext.setKey(key); 145 | txContext.setDataSource(dataSource); 146 | txContext.setId(entity.getId()); 147 | txContext.setState(entity.getState()); 148 | 149 | try { 150 | transactionRunner.run(txContext); 151 | successNum++; 152 | } catch (Throwable e) { 153 | //todo: log exception and continue execute others 154 | log.error(e.getMessage(), e); 155 | } 156 | } 157 | return successNum; 158 | } 159 | 160 | @Override 161 | public boolean isStreamingProcess() { 162 | return true; 163 | } 164 | 165 | @Override 166 | public void handleJobExecutionException(JobExecutionException jobExecutionException) throws JobExecutionException { 167 | // jobExecutionException.setUnscheduleAllTriggers(true); 168 | throw jobExecutionException; 169 | } 170 | 171 | public void setTransactionRunner(TransactionRunner transactionRunner) { 172 | this.transactionRunner = transactionRunner; 173 | } 174 | 175 | public void setDataSourceMap(Map dataSourceMap) { 176 | this.dataSourceMap = dataSourceMap; 177 | } 178 | 179 | public void setDbPrefix(String dbPrefix) { 180 | this.dbPrefix = dbPrefix; 181 | } 182 | 183 | public void setTimeoutMins(int timeoutMins) { 184 | this.timeoutMins = timeoutMins; 185 | } 186 | 187 | public void setIncludeStates(List includeStates) { 188 | this.includeStates = includeStates; 189 | } 190 | 191 | public void setExcludeStates(List excludeStates) { 192 | this.excludeStates = excludeStates; 193 | } 194 | 195 | public void setDataSource(DataSource dataSource) { 196 | this.dataSource = dataSource; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tcctx-job/src/test/java/com/jd/tx/tcc/job/TestSyncJobRetryScheduler.java: -------------------------------------------------------------------------------- 1 | package com.jd.tx.tcc.job; 2 | 3 | import org.junit.Before; 4 | 5 | /** 6 | * @author Leon Guo 7 | * Creation Date: 2016/4/27 8 | */ 9 | public class TestSyncJobRetryScheduler { 10 | 11 | private SyncJobRetryScheduler syncJobRetryScheduler; 12 | 13 | @Before 14 | public void setup() { 15 | syncJobRetryScheduler = new SyncJobRetryScheduler(); 16 | } 17 | 18 | } 19 | --------------------------------------------------------------------------------