resources) {
+ return resources.get(counter.getAndIncrement() % resources.size());
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/AppDeleteDto.java b/antares-common/src/main/java/me/hao0/antares/common/dto/AppDeleteDto.java
new file mode 100644
index 00000000..f9b9f848
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/AppDeleteDto.java
@@ -0,0 +1,29 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class AppDeleteDto implements Serializable {
+
+ private static final long serialVersionUID = 7508627524604536152L;
+
+ private String appName;
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ @Override
+ public String toString() {
+ return "AppDeleteDto{" +
+ "appName='" + appName + '\'' +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/AppSaveDto.java b/antares-common/src/main/java/me/hao0/antares/common/dto/AppSaveDto.java
new file mode 100644
index 00000000..e9487017
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/AppSaveDto.java
@@ -0,0 +1,62 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class AppSaveDto implements Serializable {
+
+ private static final long serialVersionUID = -4153657694380103021L;
+
+ private String appName;
+
+ private String appKey;
+
+ private String appDesc;
+
+ private Long inheritAppId;
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ public String getAppKey() {
+ return appKey;
+ }
+
+ public void setAppKey(String appKey) {
+ this.appKey = appKey;
+ }
+
+ public String getAppDesc() {
+ return appDesc;
+ }
+
+ public void setAppDesc(String appDesc) {
+ this.appDesc = appDesc;
+ }
+
+ public Long getInheritAppId() {
+ return inheritAppId;
+ }
+
+ public void setInheritAppId(Long inheritAppId) {
+ this.inheritAppId = inheritAppId;
+ }
+
+ @Override
+ public String toString() {
+ return "AppSaveDto{" +
+ "appName='" + appName + '\'' +
+ ", appKey='" + appKey + '\'' +
+ ", appDesc='" + appDesc + '\'' +
+ ", inheritAppId=" + inheritAppId +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/ClientInfo.java b/antares-common/src/main/java/me/hao0/antares/common/dto/ClientInfo.java
new file mode 100644
index 00000000..63cbe081
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/ClientInfo.java
@@ -0,0 +1,30 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class ClientInfo implements Serializable {
+
+ private static final long serialVersionUID = -3395261718901387010L;
+
+ private String addr;
+
+ public String getAddr() {
+ return addr;
+ }
+
+ public void setAddr(String addr) {
+ this.addr = addr;
+ }
+
+ @Override
+ public String toString() {
+ return "ClientInfo{" +
+ "addr='" + addr + '\'' +
+ '}';
+ }
+}
+
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JobControl.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JobControl.java
new file mode 100644
index 00000000..566bda37
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JobControl.java
@@ -0,0 +1,166 @@
+package me.hao0.antares.common.dto;
+
+import me.hao0.antares.common.model.enums.JobState;
+import java.io.Serializable;
+
+/**
+ * The job control dto
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class JobControl implements Serializable {
+
+ private static final long serialVersionUID = 8521933124536616448L;
+
+ /**
+ * The job id
+ */
+ private Long id;
+
+ /**
+ * The job class
+ */
+ private String clazz;
+
+ /**
+ * The cron expression
+ */
+ private String cron;
+
+ /**
+ * The job desc
+ */
+ private String desc;
+
+ /**
+ * The job scheduler server
+ */
+ private String scheduler;
+
+ /**
+ * The The current fire time;
+ */
+ private String fireTime;
+
+ /**
+ * The previous fire time;
+ */
+ private String prevFireTime;
+
+ /**
+ * The next fire time
+ */
+ private String nextFireTime;
+
+ /**
+ * The running state
+ * @see me.hao0.antares.common.model.enums.JobState
+ */
+ private Integer state;
+
+ /**
+ * The running state desc
+ */
+ private String stateDesc;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getClazz() {
+ return clazz;
+ }
+
+ public void setClazz(String clazz) {
+ this.clazz = clazz;
+ }
+
+ public String getCron() {
+ return cron;
+ }
+
+ public void setCron(String cron) {
+ this.cron = cron;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public String getScheduler() {
+ return scheduler;
+ }
+
+ public void setScheduler(String scheduler) {
+ this.scheduler = scheduler;
+ }
+
+ public String getFireTime() {
+ return fireTime;
+ }
+
+ public void setFireTime(String fireTime) {
+ this.fireTime = fireTime;
+ }
+
+ public String getPrevFireTime() {
+ return prevFireTime;
+ }
+
+ public void setPrevFireTime(String prevFireTime) {
+ this.prevFireTime = prevFireTime;
+ }
+
+ public String getNextFireTime() {
+ return nextFireTime;
+ }
+
+ public void setNextFireTime(String nextFireTime) {
+ this.nextFireTime = nextFireTime;
+ }
+
+ public Integer getState() {
+ return state;
+ }
+
+ public void setState(Integer state) {
+ this.state = state;
+ }
+
+ public void setStateAndDesc(JobState state){
+ setState(state.value());
+ setStateDesc(state.code());
+ }
+
+ public String getStateDesc() {
+ return stateDesc;
+ }
+
+ public void setStateDesc(String stateDesc) {
+ this.stateDesc = stateDesc;
+ }
+
+ @Override
+ public String toString() {
+ return "JobControl{" +
+ "id=" + id +
+ ", clazz='" + clazz + '\'' +
+ ", cron='" + cron + '\'' +
+ ", desc='" + desc + '\'' +
+ ", scheduler='" + scheduler + '\'' +
+ ", fireTime='" + fireTime + '\'' +
+ ", prevFireTime='" + prevFireTime + '\'' +
+ ", nextFireTime='" + nextFireTime + '\'' +
+ ", state=" + state +
+ ", stateDesc='" + stateDesc + '\'' +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JobDetail.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JobDetail.java
new file mode 100644
index 00000000..cdace838
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JobDetail.java
@@ -0,0 +1,55 @@
+package me.hao0.antares.common.dto;
+
+import me.hao0.antares.common.model.App;
+import me.hao0.antares.common.model.Job;
+import me.hao0.antares.common.model.JobConfig;
+import java.io.Serializable;
+
+/**
+ * The job detail
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class JobDetail implements Serializable {
+
+ private static final long serialVersionUID = 2059649679491717955L;
+
+ private App app;
+
+ private Job job;
+
+ private JobConfig config;
+
+ public App getApp() {
+ return app;
+ }
+
+ public void setApp(App app) {
+ this.app = app;
+ }
+
+ public Job getJob() {
+ return job;
+ }
+
+ public void setJob(Job job) {
+ this.job = job;
+ }
+
+ public JobConfig getConfig() {
+ return config;
+ }
+
+ public void setConfig(JobConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ public String toString() {
+ return "JobDetail{" +
+ "app=" + app +
+ ", job=" + job +
+ ", config=" + config +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JobEditDto.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JobEditDto.java
new file mode 100644
index 00000000..1f026d52
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JobEditDto.java
@@ -0,0 +1,140 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * The job Edit dto
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class JobEditDto implements Serializable {
+
+ private static final long serialVersionUID = -5130877794594938052L;
+
+ private Long appId;
+
+ private Long jobId;
+
+ private Boolean status;
+
+ private String clazz;
+
+ private String cron;
+
+ private String desc;
+
+ private Integer maxShardPullCount;
+
+ private String param;
+
+ private Integer shardCount;
+
+ private String shardParams;
+
+ private Boolean misfire;
+
+ public Long getAppId() {
+ return appId;
+ }
+
+ public void setAppId(Long appId) {
+ this.appId = appId;
+ }
+
+ public Long getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(Long jobId) {
+ this.jobId = jobId;
+ }
+
+ public Boolean getStatus() {
+ return status;
+ }
+
+ public void setStatus(Boolean status) {
+ this.status = status;
+ }
+
+ public String getClazz() {
+ return clazz;
+ }
+
+ public void setClazz(String clazz) {
+ this.clazz = clazz;
+ }
+
+ public String getCron() {
+ return cron;
+ }
+
+ public void setCron(String cron) {
+ this.cron = cron;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public Integer getMaxShardPullCount() {
+ return maxShardPullCount;
+ }
+
+ public void setMaxShardPullCount(Integer maxShardPullCount) {
+ this.maxShardPullCount = maxShardPullCount;
+ }
+
+ public String getParam() {
+ return param;
+ }
+
+ public void setParam(String param) {
+ this.param = param;
+ }
+
+ public Integer getShardCount() {
+ return shardCount;
+ }
+
+ public void setShardCount(Integer shardCount) {
+ this.shardCount = shardCount;
+ }
+
+ public String getShardParams() {
+ return shardParams;
+ }
+
+ public void setShardParams(String shardParams) {
+ this.shardParams = shardParams;
+ }
+
+ public Boolean getMisfire() {
+ return misfire;
+ }
+
+ public void setMisfire(Boolean misfire) {
+ this.misfire = misfire;
+ }
+
+ @Override
+ public String toString() {
+ return "JobEditDto{" +
+ "appId=" + appId +
+ ", jobId=" + jobId +
+ ", status=" + status +
+ ", clazz='" + clazz + '\'' +
+ ", cron='" + cron + '\'' +
+ ", desc='" + desc + '\'' +
+ ", maxShardPullCount=" + maxShardPullCount +
+ ", param='" + param + '\'' +
+ ", shardCount=" + shardCount +
+ ", shardParams='" + shardParams + '\'' +
+ ", misfire=" + misfire +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JobFireTime.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JobFireTime.java
new file mode 100644
index 00000000..5e5a080c
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JobFireTime.java
@@ -0,0 +1,60 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class JobFireTime implements Serializable {
+
+ private static final long serialVersionUID = 4612715888992171290L;
+
+ /**
+ * The current fire time
+ */
+ private String current;
+
+ /**
+ * The previous fire time
+ */
+ private String prev;
+
+ /**
+ * The next fire time
+ */
+ private String next;
+
+ public String getCurrent() {
+ return current;
+ }
+
+ public void setCurrent(String current) {
+ this.current = current;
+ }
+
+ public String getPrev() {
+ return prev;
+ }
+
+ public void setPrev(String prev) {
+ this.prev = prev;
+ }
+
+ public String getNext() {
+ return next;
+ }
+
+ public void setNext(String next) {
+ this.next = next;
+ }
+
+ @Override
+ public String toString() {
+ return "JobFireTime{" +
+ "current='" + current + '\'' +
+ ", prev='" + prev + '\'' +
+ ", next='" + next + '\'' +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceDetail.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceDetail.java
new file mode 100644
index 00000000..4ef11c2f
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceDetail.java
@@ -0,0 +1,207 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * The job running detail for monitor
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ *
+ * NOTE: This instance will be the one id is minimal, if there're multiple instances at the same time.
+ *
+ */
+public class JobInstanceDetail implements Serializable {
+
+ private static final long serialVersionUID = -3208492213218789547L;
+
+ /**
+ * The job id
+ */
+ private Long jobId;
+
+ /**
+ * The job instance id
+ */
+ private Long instanceId;
+
+ /**
+ * The job instance status
+ */
+ private Integer status;
+
+ /**
+ * The job instance status desc
+ */
+ private String statusDesc;
+
+ /**
+ * The cause when failed
+ */
+ private String cause;
+
+ /**
+ * The instance execution start time
+ */
+ private String startTime;
+
+ /**
+ * The instance execution end time
+ */
+ private String endTime;
+
+ /**
+ * The total shard count
+ *
+ * totalShardCount = successShardCount + failedShardCount + waitShardCount
+ *
+ */
+ private Integer totalShardCount;
+
+ /**
+ * The wait pull shard count
+ */
+ private Integer waitShardCount;
+
+ /**
+ * The running shard count
+ */
+ private Integer runningShardCount;
+
+ /**
+ * The success shard count
+ */
+ private Integer successShardCount;
+
+ /**
+ * The failed shard count
+ */
+ private Integer failedShardCount;
+
+ /**
+ * The finish percent: finishShardCount * 100 / totalShardCount
+ */
+ private Integer finishPercent;
+
+ public Long getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(Long jobId) {
+ this.jobId = jobId;
+ }
+
+ public Long getInstanceId() {
+ return instanceId;
+ }
+
+ public void setInstanceId(Long instanceId) {
+ this.instanceId = instanceId;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public String getStatusDesc() {
+ return statusDesc;
+ }
+
+ public void setStatusDesc(String statusDesc) {
+ this.statusDesc = statusDesc;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public void setCause(String cause) {
+ this.cause = cause;
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(String startTime) {
+ this.startTime = startTime;
+ }
+
+ public String getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(String endTime) {
+ this.endTime = endTime;
+ }
+
+ public Integer getTotalShardCount() {
+ return totalShardCount;
+ }
+
+ public void setTotalShardCount(Integer totalShardCount) {
+ this.totalShardCount = totalShardCount;
+ }
+
+ public Integer getSuccessShardCount() {
+ return successShardCount;
+ }
+
+ public void setSuccessShardCount(Integer successShardCount) {
+ this.successShardCount = successShardCount;
+ }
+
+ public Integer getFailedShardCount() {
+ return failedShardCount;
+ }
+
+ public void setFailedShardCount(Integer failedShardCount) {
+ this.failedShardCount = failedShardCount;
+ }
+
+ public Integer getWaitShardCount() {
+ return waitShardCount;
+ }
+
+ public void setWaitShardCount(Integer waitShardCount) {
+ this.waitShardCount = waitShardCount;
+ }
+
+ public Integer getRunningShardCount() {
+ return runningShardCount;
+ }
+
+ public void setRunningShardCount(Integer runningShardCount) {
+ this.runningShardCount = runningShardCount;
+ }
+
+ public Integer getFinishPercent() {
+ return finishPercent;
+ }
+
+ public void setFinishPercent(Integer finishPercent) {
+ this.finishPercent = finishPercent;
+ }
+
+ @Override
+ public String toString() {
+ return "JobInstanceDetail{" +
+ "jobId=" + jobId +
+ ", instanceId=" + instanceId +
+ ", status=" + status +
+ ", statusDesc='" + statusDesc + '\'' +
+ ", cause='" + cause + '\'' +
+ ", startTime='" + startTime + '\'' +
+ ", endTime='" + endTime + '\'' +
+ ", totalShardCount=" + totalShardCount +
+ ", waitShardCount=" + waitShardCount +
+ ", runningShardCount=" + runningShardCount +
+ ", successShardCount=" + successShardCount +
+ ", failedShardCount=" + failedShardCount +
+ ", finishPercent=" + finishPercent +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceDto.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceDto.java
new file mode 100644
index 00000000..3dc1cf34
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceDto.java
@@ -0,0 +1,149 @@
+package me.hao0.antares.common.dto;
+
+import me.hao0.antares.common.model.enums.JobInstanceStatus;
+import java.io.Serializable;
+
+/**
+ * The job instance dto
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class JobInstanceDto implements Serializable {
+
+ private static final long serialVersionUID = 119889051080239175L;
+
+ /**
+ * The instance id
+ */
+ private Long id;
+
+ /**
+ * The job id
+ */
+ private Long jobId;
+
+ /**
+ * The job instance status
+ * @see JobInstanceStatus
+ */
+ private Integer status;
+
+ /**
+ * The status desc
+ */
+ private String statusDesc;
+
+ /**
+ * The server, scheduling this job
+ */
+ private String server;
+
+ /**
+ * The instance execution start time
+ */
+ private String startTime;
+
+ /**
+ * The instance execution end time
+ */
+ private String endTime;
+
+ /**
+ * The cost time:
+ *
+ * endTime - startTime
+ *
+ */
+ private String costTime;
+
+ /**
+ * The cause when failed
+ */
+ private String cause;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(Long jobId) {
+ this.jobId = jobId;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public String getStatusDesc() {
+ return statusDesc;
+ }
+
+ public void setStatusDesc(String statusDesc) {
+ this.statusDesc = statusDesc;
+ }
+
+ public String getServer() {
+ return server;
+ }
+
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(String startTime) {
+ this.startTime = startTime;
+ }
+
+ public String getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(String endTime) {
+ this.endTime = endTime;
+ }
+
+ public String getCostTime() {
+ return costTime;
+ }
+
+ public void setCostTime(String costTime) {
+ this.costTime = costTime;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public void setCause(String cause) {
+ this.cause = cause;
+ }
+
+ @Override
+ public String toString() {
+ return "JobInstanceDto{" +
+ "id=" + id +
+ ", jobId=" + jobId +
+ ", status=" + status +
+ ", statusDesc='" + statusDesc + '\'' +
+ ", server='" + server + '\'' +
+ ", startTime='" + startTime + '\'' +
+ ", endTime='" + endTime + '\'' +
+ ", cause='" + cause + '\'' +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceShardDto.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceShardDto.java
new file mode 100644
index 00000000..547d54a6
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JobInstanceShardDto.java
@@ -0,0 +1,205 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * The job instance shard
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class JobInstanceShardDto implements Serializable {
+
+ private static final long serialVersionUID = 4699655089712303564L;
+
+ /**
+ * The primary key
+ */
+ private Long id;
+
+ /**
+ * The job instance id
+ */
+ private Long instanceId;
+
+ /**
+ * The shard item index
+ */
+ private Integer item;
+
+ /**
+ * The shard param
+ */
+ private String param;
+
+ /**
+ * The pull client
+ */
+ private String pullClient;
+
+ /**
+ * The finish client
+ */
+ private String finishClient;
+
+ /**
+ * The status
+ * @see me.hao0.antares.common.model.enums.JobInstanceShardStatus
+ */
+ private Integer status;
+
+ /**
+ * The status desc
+ */
+ private String statusDesc;
+
+ /**
+ * The cause when failed
+ */
+ private String cause;
+
+ /**
+ * The shard is pulled by total count:
+ *
+ * 1 : normal
+ * >1 : has failed execution
+ */
+ private Integer pullCount;
+
+ /**
+ * The pullClient pull time;
+ */
+ private String pullTime;
+
+ /**
+ * The pullClient executing start time
+ */
+ private String startTime;
+
+ /**
+ * The pullClient executing end time
+ */
+ private String endTime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getInstanceId() {
+ return instanceId;
+ }
+
+ public void setInstanceId(Long instanceId) {
+ this.instanceId = instanceId;
+ }
+
+ public Integer getItem() {
+ return item;
+ }
+
+ public void setItem(Integer item) {
+ this.item = item;
+ }
+
+ public String getParam() {
+ return param;
+ }
+
+ public void setParam(String param) {
+ this.param = param;
+ }
+
+ public String getPullClient() {
+ return pullClient;
+ }
+
+ public void setPullClient(String pullClient) {
+ this.pullClient = pullClient;
+ }
+
+ public String getFinishClient() {
+ return finishClient;
+ }
+
+ public void setFinishClient(String finishClient) {
+ this.finishClient = finishClient;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public String getStatusDesc() {
+ return statusDesc;
+ }
+
+ public void setStatusDesc(String statusDesc) {
+ this.statusDesc = statusDesc;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public void setCause(String cause) {
+ this.cause = cause;
+ }
+
+ public Integer getPullCount() {
+ return pullCount;
+ }
+
+ public void setPullCount(Integer pullCount) {
+ this.pullCount = pullCount;
+ }
+
+ public String getPullTime() {
+ return pullTime;
+ }
+
+ public void setPullTime(String pullTime) {
+ this.pullTime = pullTime;
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(String startTime) {
+ this.startTime = startTime;
+ }
+
+ public String getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(String endTime) {
+ this.endTime = endTime;
+ }
+
+ @Override
+ public String toString() {
+ return "JobInstanceShardDto{" +
+ "id=" + id +
+ ", instanceId=" + instanceId +
+ ", item=" + item +
+ ", param='" + param + '\'' +
+ ", pullClient='" + pullClient + '\'' +
+ ", finishClient='" + finishClient + '\'' +
+ ", status=" + status +
+ ", statusDesc='" + statusDesc + '\'' +
+ ", pullTime=" + pullTime +
+ ", pullCount=" + pullCount +
+ ", startTime=" + startTime +
+ ", endTime=" + endTime +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/JsonResponse.java b/antares-common/src/main/java/me/hao0/antares/common/dto/JsonResponse.java
new file mode 100644
index 00000000..53022dd8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/JsonResponse.java
@@ -0,0 +1,107 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * Json Response
+ */
+public class JsonResponse implements Serializable {
+
+ private static final long serialVersionUID = -4761871227325502579L;
+
+ public static final Integer OK = 200;
+
+ public static final Integer REDIRECT = 302;
+
+ public static final Integer ERR = 500;
+
+ public static final JsonResponse NEED_LOGIN = JsonResponse.notOk(403, "用户未登录");
+
+ public static final JsonResponse AUTH_FAIL = JsonResponse.notOk(401, "认证失败");
+
+ public static final JsonResponse PARAM_MISSING = JsonResponse.notOk(400, "参数缺失");
+
+ public static final JsonResponse SERVER_ERR = JsonResponse.notOk(ERR, "服务器异常");
+
+ /**
+ * 响应码
+ */
+ private Integer status;
+
+ /**
+ * 错误信息
+ */
+ private Object err;
+
+ /**
+ * 响应数据
+ */
+ private T data;
+
+ public static JsonResponse ok(){
+ JsonResponse r = new JsonResponse();
+ r.status = OK;
+ return r;
+ }
+
+ public static JsonResponse ok(Object data){
+ JsonResponse r = new JsonResponse();
+ r.status = OK;
+ r.data = data;
+ return r;
+ }
+
+ public static JsonResponse notOk(Object err){
+ JsonResponse r = new JsonResponse();
+ r.status = ERR;
+ r.err = err;
+ return r;
+ }
+
+ public static JsonResponse notOk(Integer status, Object err){
+ JsonResponse r = new JsonResponse();
+ r.status = status;
+ r.err = err;
+ return r;
+ }
+
+ public static JsonResponse redirect(String url){
+ JsonResponse r = new JsonResponse();
+ r.status = REDIRECT;
+ r.data = url;
+ return r;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public Object getErr() {
+ return err;
+ }
+
+ public void setErr(Object err) {
+ this.err = err;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public void setData(T data) {
+ this.data = data;
+ }
+
+ public Boolean isSuccess(){
+ return status.intValue() == OK.intValue();
+ }
+
+ @Override
+ public String toString() {
+ return "JsonResponse{" +
+ "status=" + status +
+ ", err=" + err +
+ ", data=" + data +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/PullShard.java b/antares-common/src/main/java/me/hao0/antares/common/dto/PullShard.java
new file mode 100644
index 00000000..30855900
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/PullShard.java
@@ -0,0 +1,88 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class PullShard implements Serializable {
+
+ private static final long serialVersionUID = -7899746031432616077L;
+
+ /**
+ * The shard id
+ */
+ private Long id;
+
+ /**
+ * The shard item index
+ */
+ private Integer item;
+
+ /**
+ * The shard param
+ */
+ private String param;
+
+ /**
+ * The job param
+ */
+ private String jobParam;
+
+ /**
+ * The total shard count
+ */
+ private Integer totalShardCount;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Integer getItem() {
+ return item;
+ }
+
+ public void setItem(Integer item) {
+ this.item = item;
+ }
+
+ public String getParam() {
+ return param;
+ }
+
+ public void setParam(String param) {
+ this.param = param;
+ }
+
+ public String getJobParam() {
+ return jobParam;
+ }
+
+ public void setJobParam(String jobParam) {
+ this.jobParam = jobParam;
+ }
+
+ public Integer getTotalShardCount() {
+ return totalShardCount;
+ }
+
+ public void setTotalShardCount(Integer totalShardCount) {
+ this.totalShardCount = totalShardCount;
+ }
+
+ @Override
+ public String toString() {
+ return "PullShard{" +
+ "id=" + id +
+ ", item=" + item +
+ ", param='" + param + '\'' +
+ ", jobParam='" + jobParam + '\'' +
+ ", totalShardCount=" + totalShardCount +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/ServerInfo.java b/antares-common/src/main/java/me/hao0/antares/common/dto/ServerInfo.java
new file mode 100644
index 00000000..290fa6d2
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/ServerInfo.java
@@ -0,0 +1,60 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class ServerInfo implements Serializable {
+
+ private static final long serialVersionUID = 5143623538738976199L;
+
+ /**
+ * The server host
+ */
+ private String server;
+
+ /**
+ * Is the leader or not
+ */
+ private Boolean leader;
+
+ /**
+ * The scheduling jobs count
+ */
+ private Integer jobCount;
+
+ public String getServer() {
+ return server;
+ }
+
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ public Boolean getLeader() {
+ return leader;
+ }
+
+ public void setLeader(Boolean leader) {
+ this.leader = leader;
+ }
+
+ public Integer getJobCount() {
+ return jobCount;
+ }
+
+ public void setJobCount(Integer jobCount) {
+ this.jobCount = jobCount;
+ }
+
+ @Override
+ public String toString() {
+ return "ServerInfo{" +
+ "server='" + server + '\'' +
+ ", leader=" + leader +
+ ", jobCount=" + jobCount +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/ShardFinishDto.java b/antares-common/src/main/java/me/hao0/antares/common/dto/ShardFinishDto.java
new file mode 100644
index 00000000..b01e84f3
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/ShardFinishDto.java
@@ -0,0 +1,117 @@
+package me.hao0.antares.common.dto;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class ShardFinishDto implements Serializable {
+
+ private static final long serialVersionUID = 7852332761131470264L;
+
+ /**
+ * The client host
+ */
+ private String client;
+
+ /**
+ * The job instance id
+ */
+ private Long instanceId;
+
+ /**
+ * The shard id
+ */
+ private Long shardId;
+
+ /**
+ * The shard start time
+ */
+ private Date startTime;
+
+ /**
+ * The shard end time
+ */
+ private Date endTime;
+
+ /**
+ * Finish success or false
+ */
+ private Boolean success = Boolean.TRUE;
+
+ /**
+ * The cause when failed
+ */
+ private String cause;
+
+ public String getClient() {
+ return client;
+ }
+
+ public void setClient(String client) {
+ this.client = client;
+ }
+
+ public Long getInstanceId() {
+ return instanceId;
+ }
+
+ public void setInstanceId(Long instanceId) {
+ this.instanceId = instanceId;
+ }
+
+ public Long getShardId() {
+ return shardId;
+ }
+
+ public void setShardId(Long shardId) {
+ this.shardId = shardId;
+ }
+
+ public Date getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+
+ public Date getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(Date endTime) {
+ this.endTime = endTime;
+ }
+
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ public void setSuccess(Boolean success) {
+ this.success = success;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public void setCause(String cause) {
+ this.cause = cause;
+ }
+
+ @Override
+ public String toString() {
+ return "ShardFinishDto{" +
+ "client='" + client + '\'' +
+ ", instanceId=" + instanceId +
+ ", shardId=" + shardId +
+ ", startTime=" + startTime +
+ ", endTime=" + endTime +
+ ", success=" + success +
+ ", cause='" + cause + '\'' +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/ShardOperateResp.java b/antares-common/src/main/java/me/hao0/antares/common/dto/ShardOperateResp.java
new file mode 100644
index 00000000..05c4c5e8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/ShardOperateResp.java
@@ -0,0 +1,47 @@
+package me.hao0.antares.common.dto;
+
+import me.hao0.antares.common.model.enums.ShardOperateRespCode;
+
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class ShardOperateResp implements Serializable {
+
+ private static final long serialVersionUID = 2675738340828402708L;
+
+ private ShardOperateRespCode code;
+
+ private Boolean success;
+
+ public ShardOperateResp(ShardOperateRespCode code, Boolean success) {
+ this.code = code;
+ this.success = success;
+ }
+
+ public ShardOperateRespCode getCode() {
+ return code;
+ }
+
+ public void setCode(ShardOperateRespCode code) {
+ this.code = code;
+ }
+
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ public void setSuccess(Boolean success) {
+ this.success = success;
+ }
+
+ @Override
+ public String toString() {
+ return "FinishShardResp{" +
+ "code=" + code +
+ ", success=" + success +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/dto/ShardPullResp.java b/antares-common/src/main/java/me/hao0/antares/common/dto/ShardPullResp.java
new file mode 100644
index 00000000..2b58ebd1
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/dto/ShardPullResp.java
@@ -0,0 +1,46 @@
+package me.hao0.antares.common.dto;
+
+import me.hao0.antares.common.model.enums.ShardOperateRespCode;
+import java.io.Serializable;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class ShardPullResp implements Serializable {
+
+ private static final long serialVersionUID = 2675738340828402708L;
+
+ private ShardOperateRespCode code;
+
+ private PullShard pullShard;
+
+ public ShardPullResp(ShardOperateRespCode code, PullShard pullShard) {
+ this.code = code;
+ this.pullShard = pullShard;
+ }
+
+ public ShardOperateRespCode getCode() {
+ return code;
+ }
+
+ public void setCode(ShardOperateRespCode code) {
+ this.code = code;
+ }
+
+ public PullShard getPullShard() {
+ return pullShard;
+ }
+
+ public void setPullShard(PullShard pullShard) {
+ this.pullShard = pullShard;
+ }
+
+ @Override
+ public String toString() {
+ return "PullShardResp{" +
+ "code=" + code +
+ ", pullShard=" + pullShard +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/exception/HttpException.java b/antares-common/src/main/java/me/hao0/antares/common/exception/HttpException.java
new file mode 100644
index 00000000..cf194ef6
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/exception/HttpException.java
@@ -0,0 +1,27 @@
+package me.hao0.antares.common.exception;
+
+/**
+ * @author haolin
+ * @mailto haolin.h0@gmail.com
+ */
+public class HttpException extends RuntimeException {
+ public HttpException() {
+ super();
+ }
+
+ public HttpException(String message) {
+ super(message);
+ }
+
+ public HttpException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public HttpException(Throwable cause) {
+ super(cause);
+ }
+
+ protected HttpException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/exception/JobFindException.java b/antares-common/src/main/java/me/hao0/antares/common/exception/JobFindException.java
new file mode 100644
index 00000000..2a9be9db
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/exception/JobFindException.java
@@ -0,0 +1,27 @@
+package me.hao0.antares.common.exception;
+
+/**
+ * @author haolin
+ * @mailto haolin.h0@gmail.com
+ */
+public class JobFindException extends RuntimeException {
+ public JobFindException() {
+ super();
+ }
+
+ public JobFindException(String message) {
+ super(message);
+ }
+
+ public JobFindException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public JobFindException(Throwable cause) {
+ super(cause);
+ }
+
+ protected JobFindException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/exception/JobStateTransferInvalidException.java b/antares-common/src/main/java/me/hao0/antares/common/exception/JobStateTransferInvalidException.java
new file mode 100644
index 00000000..485d63ea
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/exception/JobStateTransferInvalidException.java
@@ -0,0 +1,28 @@
+package me.hao0.antares.common.exception;
+
+import me.hao0.antares.common.model.enums.JobState;
+
+/**
+ * @author haolin
+ * @mailto haolin.h0@gmail.com
+ */
+public class JobStateTransferInvalidException extends RuntimeException {
+
+ private String id;
+
+ private JobState current;
+
+ private JobState target;
+
+ public JobStateTransferInvalidException(String id, JobState current, JobState target){
+ super();
+ this.id = id;
+ this.current = current;
+ this.target = target;
+ }
+
+ @Override
+ public String toString() {
+ return "JOB(" + id + "): " + current + " --> " + target;
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/exception/ZkException.java b/antares-common/src/main/java/me/hao0/antares/common/exception/ZkException.java
new file mode 100644
index 00000000..c62b2e80
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/exception/ZkException.java
@@ -0,0 +1,27 @@
+package me.hao0.antares.common.exception;
+
+/**
+ * @author haolin
+ * @mailto haolin.h0@gmail.com
+ */
+public class ZkException extends RuntimeException {
+ public ZkException() {
+ super();
+ }
+
+ public ZkException(String message) {
+ super(message);
+ }
+
+ public ZkException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ZkException(Throwable cause) {
+ super(cause);
+ }
+
+ protected ZkException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/http/Http.java b/antares-common/src/main/java/me/hao0/antares/common/http/Http.java
new file mode 100644
index 00000000..e20e8cb8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/http/Http.java
@@ -0,0 +1,273 @@
+package me.hao0.antares.common.http;
+
+import com.github.kevinsawicki.http.HttpRequest;
+import com.google.common.base.Strings;
+import me.hao0.antares.common.exception.HttpException;
+import java.io.*;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * The Http client
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class Http {
+
+ private String url;
+
+ private HttpMethod method = HttpMethod.GET;
+
+ private Map headers = Collections.emptyMap();
+
+ private Map params = Collections.emptyMap();
+
+ /**
+ * The request body
+ */
+ private String body;
+
+ private Boolean ssl = Boolean.FALSE;
+
+ private Integer connectTimeout = 1000 * 5;
+
+ private Integer readTimeout = 1000 * 5;
+
+ /**
+ * Encode or not
+ */
+ private Boolean encode = Boolean.TRUE;
+
+ /**
+ * The content type
+ */
+ private String contentType = "";
+
+ /**
+ * The charset
+ */
+ private String charset = HttpRequest.CHARSET_UTF8;
+
+ /**
+ * The accept type
+ */
+ private String accept = "";
+
+ private Http(String url){
+ this.url = url;
+ }
+
+ private Http method(HttpMethod method){
+ this.method = method;
+ return this;
+ }
+
+ public Http ssl(){
+ this.ssl = Boolean.TRUE;
+ return this;
+ }
+
+ public Http headers(Map headers){
+ this.headers = headers;
+ return this;
+ }
+
+ public Http params(Map params){
+ this.params = params;
+ return this;
+ }
+
+ /**
+ * The request body
+ * @param body request body
+ * @return this
+ */
+ public Http body(String body){
+ this.body = body;
+ return this;
+ }
+
+ public Http encode(Boolean encode){
+ this.encode = encode;
+ return this;
+ }
+
+ public Http contentType(String contentType){
+ this.contentType = contentType;
+ return this;
+ }
+
+ public Http charset(String charset){
+ this.charset = charset;
+ return this;
+ }
+
+ public Http accept(String accept){
+ this.accept = accept;
+ return this;
+ }
+
+ /**
+ * set connect timeout
+ * @param connectTimeout (s)
+ * @return this
+ */
+ public Http connTimeout(Integer connectTimeout){
+ this.connectTimeout = connectTimeout * 1000;
+ return this;
+ }
+
+ /**
+ * set read timeout
+ * @param readTimeout (s)
+ * @return this
+ */
+ public Http readTimeout(Integer readTimeout){
+ this.readTimeout = readTimeout * 1000;
+ return this;
+ }
+
+ public String request(){
+ switch (method){
+ case GET:
+ return doGet();
+ case POST:
+ return doPost();
+ default:
+ break;
+ }
+ return null;
+ }
+
+
+ private String doPost() {
+ HttpRequest post = HttpRequest.post(url, params, encode)
+ .headers(headers)
+ .connectTimeout(connectTimeout)
+ .readTimeout(readTimeout)
+ .acceptGzipEncoding()
+ .uncompress(true);
+ setOptionalHeaders(post);
+
+ if (!Strings.isNullOrEmpty(body)){
+ post.send(body);
+ }
+
+ if (ssl){
+ trustHttps(post);
+ }
+
+ return post.body();
+ }
+
+ private String doGet() {
+ HttpRequest get = HttpRequest.get(url, params, encode)
+ .headers(headers)
+ .connectTimeout(connectTimeout)
+ .readTimeout(readTimeout)
+ .acceptGzipEncoding()
+ .uncompress(true);
+ if (ssl){
+ trustHttps(get);
+ }
+ setOptionalHeaders(get);
+ return get.body();
+ }
+
+ private void setOptionalHeaders(HttpRequest request) {
+ if (!Strings.isNullOrEmpty(contentType)){
+ request.contentType(contentType, charset);
+ }
+ if (!Strings.isNullOrEmpty(accept)){
+ request.accept(accept);
+ }
+ }
+
+ private void trustHttps(HttpRequest request) {
+ request.trustAllCerts().trustAllHosts();
+ }
+
+ public static Http get(String url){
+ return new Http(url);
+ }
+
+ public static Http post(String url){
+ return new Http(url).method(HttpMethod.POST);
+ }
+
+ public static Http put(String url){
+ return new Http(url).method(HttpMethod.PUT);
+ }
+
+ public static Http delete(String url){
+ return new Http(url).method(HttpMethod.DELETE);
+ }
+
+ /**
+ * upload file
+ * @param url url
+ * @param fieldName field name
+ * @param fileName file name
+ * @param data file byte data
+ * @param params other params
+ * @return string response
+ */
+ public static String upload(String url, String fieldName, String fileName, byte[] data, Map params){
+ return upload(url, fieldName, fileName, new ByteArrayInputStream(data), params);
+ }
+
+ /**
+ * upload file
+ * @param url url
+ * @param fieldName field name
+ * @param fileName file name
+ * @param in InputStream
+ * @param params other params
+ * @return string response
+ */
+ public static String upload(String url, String fieldName, String fileName, InputStream in, Map params){
+ try {
+ HttpRequest request = HttpRequest.post(url);
+ if (!params.isEmpty()){
+ for (Map.Entry param : params.entrySet()){
+ request.part(param.getKey(), param.getValue());
+ }
+ }
+ request.part(fieldName , fileName, null, in);
+ return request.body();
+ } catch (Exception e){
+ throw new HttpException(e);
+ }
+ }
+
+ /**
+ * download a file
+ * @param url http url
+ * @param into the file which downloaded content will fill into
+ */
+ public static void download(String url, File into){
+ try {
+ download(url, new FileOutputStream(into));
+ } catch (FileNotFoundException e) {
+ throw new HttpException(e);
+ }
+ }
+
+ /**
+ * download a file
+ * @param url http url
+ * @param output the output which downloaded content will fill into
+ */
+ public static void download(String url, OutputStream output){
+ try {
+ HttpRequest request = HttpRequest.get(url);
+ if (request.ok()){
+ request.receive(output);
+ } else {
+ throw new HttpException("request isn't ok: " + request.body());
+ }
+ } catch (Exception e){
+ throw new HttpException(e);
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/http/HttpMethod.java b/antares-common/src/main/java/me/hao0/antares/common/http/HttpMethod.java
new file mode 100644
index 00000000..f4c9506b
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/http/HttpMethod.java
@@ -0,0 +1,5 @@
+package me.hao0.antares.common.http;
+
+public enum HttpMethod {
+ GET, POST, PUT, DELETE
+}
\ No newline at end of file
diff --git a/antares-common/src/main/java/me/hao0/antares/common/http/Https.java b/antares-common/src/main/java/me/hao0/antares/common/http/Https.java
new file mode 100644
index 00000000..61e6d73e
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/http/Https.java
@@ -0,0 +1,248 @@
+package me.hao0.antares.common.http;
+
+import com.github.kevinsawicki.http.HttpRequest;
+import com.google.common.base.Strings;
+import me.hao0.antares.common.exception.HttpException;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * The Https client
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public final class Https {
+
+ private HttpsURLConnection connection;
+
+ private int connectTimeout = 1000 * 5;
+
+ private int readTimeout = 1000 * 5;
+
+ private String contentType = "";
+
+ private String acceptCharset = "UTF-8";
+
+ private String acceptType = "text/plain";
+
+ private String connectType = "close";
+
+ private String body = "";
+
+ private String bodyCharset = "UTF-8";
+
+ private boolean encode = true;
+
+ private boolean gzip = false;
+
+
+ private Https(){}
+
+ /**
+ * 设置连接超时,默认5s
+ * @param secs 秒
+ * @return this
+ */
+ public Https connectTimeout(int secs){
+ connection.setConnectTimeout(secs * 1000);
+ return this;
+ }
+
+ /**
+ * 设置读取超时,默认5s
+ * @param secs 秒
+ * @return this
+ */
+ public Https readTimeout(int secs){
+ readTimeout = secs * 1000;
+ return this;
+ }
+
+ /**
+ * 设置Content-Type
+ * @param contentType contentType
+ * @return this
+ */
+ public Https contentType(String contentType){
+ this.contentType = contentType;
+ return this;
+ }
+
+ /**
+ * 设置Accept
+ * @param acceptType accessType,默认text/plain
+ * @return this
+ */
+ public Https acceptType(String acceptType){
+ this.acceptType = acceptType;
+ return this;
+ }
+
+ /**
+ * 设置Accept-Charset
+ * @param acceptCharset 默认UTF-8
+ * @return this
+ */
+ public Https acceptCharset(String acceptCharset){
+ this.acceptCharset = acceptCharset;
+ return this;
+ }
+
+ /**
+ * 设置Connection
+ * @param connectType 默认close
+ * @return this
+ */
+ public Https connectType(String connectType){
+ this.connectType = connectType;
+ return this;
+ }
+
+ /**
+ * 设置UseCaches
+ * @param useCache use cache or not
+ * @return this
+ */
+ public Https useCache(boolean useCache){
+ connection.setUseCaches(useCache);
+ return this;
+ }
+
+ /**
+ * 设置request body
+ * @param body request body
+ * @return this
+ */
+ public Https body(String body){
+ this.body = body;
+ return this;
+ }
+
+ /**
+ * 设置request body字符编码
+ * @param charset 字符编码
+ * @return this
+ */
+ public Https bodyCharset(String charset){
+ this.bodyCharset = charset;
+ return this;
+ }
+
+ /**
+ * 设置SSLSocketFactory
+ * @param factory SSLSocketFactory
+ * @return this
+ */
+ public Https ssLSocketFactory(SSLSocketFactory factory){
+ connection.setSSLSocketFactory(factory);
+ return this;
+ }
+
+ /**
+ * 设置请求header
+ * @param name 名称
+ * @param value 值
+ * @return this
+ */
+ public Https header(final String name, final String value) {
+ connection.setRequestProperty(name, value);
+ return this;
+ }
+
+ /**
+ * 获取请求响应内容
+ * @return 响应内容
+ */
+ public String request(){
+ prepareRequest();
+ return doRequest();
+ }
+
+ private void prepareRequest() {
+
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+ connection.setConnectTimeout(connectTimeout);
+ connection.setReadTimeout(readTimeout);
+ header("Accept-Charset", acceptCharset);
+ header("Connection", connectType);
+
+ if (gzip){
+ header("Accept-Encoding", "gzip, deflate");
+ }
+ if (!Strings.isNullOrEmpty(contentType)){
+ header("Content-Type", connectType);
+ }
+ if (!Strings.isNullOrEmpty(acceptType)){
+ header("Accept", acceptType);
+ }
+
+ if (!Strings.isNullOrEmpty(body)){
+ header("Content-Length", String.valueOf(body.length()));
+ }
+ }
+
+ private String doRequest() {
+ if (!Strings.isNullOrEmpty(body)){
+ try (OutputStream out = connection.getOutputStream()){
+ out.write(body.getBytes());
+ } catch (IOException e) {
+ throw new HttpException(e);
+ }
+ }
+
+ int respCode;
+ try {
+ respCode = connection.getResponseCode();
+ } catch (IOException e) {
+ throw new HttpException(e);
+ }
+
+ try (InputStream in = respCode == HttpURLConnection.HTTP_OK ?
+ connection.getInputStream() : connection.getErrorStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
+ StringBuilder content = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ content.append(line);
+ }
+ return content.toString();
+ } catch (IOException e) {
+ throw new HttpException(e);
+ }
+ }
+
+ public static Https get(String url){
+ return get(url, true);
+ }
+
+ public static Https get(String url, Boolean encode){
+ Https https = new Https();
+ https.connection = createConnection(url, "GET", encode);
+ return https;
+ }
+
+ public static Https post(String url){
+ return post(url, true);
+ }
+
+ public static Https post(String url, Boolean encode){
+ Https https = new Https();
+ https.connection = createConnection(url, "POST", encode);
+ return https;
+ }
+
+ private static HttpsURLConnection createConnection(String url, String method, Boolean encode) {
+ try {
+ URL u = new URL(encode ? HttpRequest.encode(url) : url);
+ HttpsURLConnection conn = (HttpsURLConnection) u.openConnection();
+ conn.setRequestMethod(method);
+ return conn;
+ } catch (IOException e) {
+ throw new HttpException(e);
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/log/Logs.java b/antares-common/src/main/java/me/hao0/antares/common/log/Logs.java
new file mode 100644
index 00000000..22885f83
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/log/Logs.java
@@ -0,0 +1,37 @@
+package me.hao0.antares.common.log;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public final class Logs {
+
+ private static final Logger INFO = LoggerFactory.getLogger("info");
+
+ private static final Logger WARN = LoggerFactory.getLogger("warn");
+
+ private static final Logger NOTIFY = LoggerFactory.getLogger("notify");
+
+ private static final Logger ERROR = LoggerFactory.getLogger("error");
+
+ private Logs(){}
+
+ public static void info(String msg, Object... args){
+ INFO.info(msg, args);
+ }
+
+ public static void warn(String msg, Object... args){
+ WARN.warn(msg, args);
+ }
+
+ public static void error(String msg, Object... args){
+ ERROR.error(msg, args);
+ }
+
+ public static void nofity(String msg, Object... args){
+ NOTIFY.info(msg, args);
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/App.java b/antares-common/src/main/java/me/hao0/antares/common/model/App.java
new file mode 100644
index 00000000..22388a50
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/App.java
@@ -0,0 +1,103 @@
+package me.hao0.antares.common.model;
+
+import java.util.Date;
+
+/**
+ * An application, usually an application grouping all instances of the same application
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class App implements Model {
+
+ private static final long serialVersionUID = -753263466645672377L;
+
+ /**
+ * The primary key
+ */
+ private Long id;
+
+ /**
+ * The application name
+ */
+ private String appName;
+
+ /**
+ * The appKey for some auth
+ */
+ private String appKey;
+
+ /**
+ * The description
+ */
+ private String appDesc;
+
+ /**
+ * The created time
+ */
+ private Date ctime;
+
+ /**
+ * The updated time
+ */
+ private Date utime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ public String getAppKey() {
+ return appKey;
+ }
+
+ public void setAppKey(String appKey) {
+ this.appKey = appKey;
+ }
+
+ public String getAppDesc() {
+ return appDesc;
+ }
+
+ public void setAppDesc(String appDesc) {
+ this.appDesc = appDesc;
+ }
+
+ public Date getCtime() {
+ return ctime;
+ }
+
+ public void setCtime(Date ctime) {
+ this.ctime = ctime;
+ }
+
+ public Date getUtime() {
+ return utime;
+ }
+
+ public void setUtime(Date utime) {
+ this.utime = utime;
+ }
+
+ @Override
+ public String toString() {
+ return "App{" +
+ "id=" + id +
+ ", appName='" + appName + '\'' +
+ ", appKey='" + appKey + '\'' +
+ ", appDesc='" + appDesc + '\'' +
+ ", ctime=" + ctime +
+ ", utime=" + utime +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/Job.java b/antares-common/src/main/java/me/hao0/antares/common/model/Job.java
new file mode 100644
index 00000000..4c891f56
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/Job.java
@@ -0,0 +1,150 @@
+package me.hao0.antares.common.model;
+
+import me.hao0.antares.common.model.enums.JobStatus;
+import me.hao0.antares.common.model.enums.JobType;
+
+import java.util.Date;
+
+/**
+ * The job basic info
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class Job implements Model {
+
+ private static final long serialVersionUID = 6784880080835250983L;
+
+ /**
+ * The primary key
+ */
+ private Long id;
+
+ /**
+ * The application id
+ */
+ private Long appId;
+
+ /**
+ * The job type
+ * @see JobType
+ */
+ private Integer type;
+
+ /**
+ * The job class full name
+ */
+ private String clazz;
+
+ /**
+ * The job cron expression
+ */
+ private String cron;
+
+ /**
+ * The job status
+ * @see JobStatus
+ */
+ private Integer status;
+
+ /**
+ * The job description
+ */
+ private String desc;
+
+ /**
+ * The created time
+ */
+ private Date ctime;
+
+ /**
+ * The updated time
+ */
+ private Date utime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getAppId() {
+ return appId;
+ }
+
+ public void setAppId(Long appId) {
+ this.appId = appId;
+ }
+
+ public Integer getType() {
+ return type;
+ }
+
+ public void setType(Integer type) {
+ this.type = type;
+ }
+
+ public String getClazz() {
+ return clazz;
+ }
+
+ public void setClazz(String clazz) {
+ this.clazz = clazz;
+ }
+
+ public String getCron() {
+ return cron;
+ }
+
+ public void setCron(String cron) {
+ this.cron = cron;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public Date getCtime() {
+ return ctime;
+ }
+
+ public void setCtime(Date ctime) {
+ this.ctime = ctime;
+ }
+
+ public Date getUtime() {
+ return utime;
+ }
+
+ public void setUtime(Date utime) {
+ this.utime = utime;
+ }
+
+ @Override
+ public String toString() {
+ return "Job{" +
+ "id=" + id +
+ ", appId=" + appId +
+ ", type=" + type +
+ ", clazz='" + clazz + '\'' +
+ ", cron='" + cron + '\'' +
+ ", status=" + status +
+ ", desc='" + desc + '\'' +
+ ", ctime=" + ctime +
+ ", utime=" + utime +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/JobConfig.java b/antares-common/src/main/java/me/hao0/antares/common/model/JobConfig.java
new file mode 100644
index 00000000..d84ce794
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/JobConfig.java
@@ -0,0 +1,146 @@
+package me.hao0.antares.common.model;
+
+import me.hao0.antares.common.anno.RedisModel;
+import java.util.Date;
+
+/**
+ * The job configuration info
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+@RedisModel(prefix = "job_cfgs")
+public class JobConfig implements Model {
+
+ private static final long serialVersionUID = 4800351890221647029L;
+
+ /**
+ * The primary key
+ */
+ private Long id;
+
+ /**
+ * The job id
+ */
+ private Long jobId;
+
+ /**
+ * Support misfire or not
+ */
+ private Boolean misfire;
+
+ /**
+ * The job execute param(optional)
+ */
+ private String param;
+
+ /**
+ * The sharding total count
+ */
+ private Integer shardCount;
+
+ /**
+ * The sharding param, comma separated
+ */
+ private String shardParams;
+
+ /**
+ * The shard max pull count
+ */
+ private Integer maxShardPullCount;
+
+ /**
+ * The created time
+ */
+ private Date ctime;
+
+ /**
+ * The updated time
+ */
+ private Date utime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(Long jobId) {
+ this.jobId = jobId;
+ }
+
+ public Boolean getMisfire() {
+ return misfire;
+ }
+
+ public void setMisfire(Boolean misfire) {
+ this.misfire = misfire;
+ }
+
+ public String getParam() {
+ return param;
+ }
+
+ public void setParam(String param) {
+ this.param = param;
+ }
+
+ public Integer getShardCount() {
+ return shardCount;
+ }
+
+ public void setShardCount(Integer shardCount) {
+ this.shardCount = shardCount;
+ }
+
+ public String getShardParams() {
+ return shardParams;
+ }
+
+ public void setShardParams(String shardParams) {
+ this.shardParams = shardParams;
+ }
+
+ public Integer getMaxShardPullCount() {
+ return maxShardPullCount;
+ }
+
+ public void setMaxShardPullCount(Integer maxShardPullCount) {
+ this.maxShardPullCount = maxShardPullCount;
+ }
+
+ public Date getCtime() {
+ return ctime;
+ }
+
+ public void setCtime(Date ctime) {
+ this.ctime = ctime;
+ }
+
+ public Date getUtime() {
+ return utime;
+ }
+
+ public void setUtime(Date utime) {
+ this.utime = utime;
+ }
+
+ @Override
+ public String toString() {
+ return "JobConfig{" +
+ "id=" + id +
+ ", jobId=" + jobId +
+ ", misfire=" + misfire +
+ ", param='" + param + '\'' +
+ ", shardCount=" + shardCount +
+ ", shardParams='" + shardParams + '\'' +
+ ", ctime=" + ctime +
+ ", utime=" + utime +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/JobInstance.java b/antares-common/src/main/java/me/hao0/antares/common/model/JobInstance.java
new file mode 100644
index 00000000..f71acb88
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/JobInstance.java
@@ -0,0 +1,201 @@
+package me.hao0.antares.common.model;
+
+import me.hao0.antares.common.anno.RedisModel;
+import me.hao0.antares.common.model.enums.JobInstanceStatus;
+import java.util.Date;
+
+/**
+ * The job execution instance
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+@RedisModel(prefix = "job_inss")
+public class JobInstance implements Model {
+
+ private static final long serialVersionUID = -6691569994755828004L;
+
+ /**
+ * The primary key
+ */
+ private Long id;
+
+ /**
+ * The job id
+ */
+ private Long jobId;
+
+ /**
+ * The job instance status
+ * @see JobInstanceStatus
+ */
+ private Integer status;
+
+ /**
+ * The server, scheduling this job
+ */
+ private String server;
+
+ /**
+ * The max shard pull count
+ *
+ * the snapshot of the job config's max shard pull count, because it will be updated in the future
+ *
+ */
+ private Integer maxShardPullCount;
+
+ /**
+ * The job params
+ *
+ * the snapshot of the job config's params, because it will be updated in the future
+ *
+ */
+ private String jobParam;
+
+ /**
+ * The total shard count
+ */
+ private Integer totalShardCount;
+
+ /**
+ * The instance execution start time
+ */
+ private Date startTime;
+
+ /**
+ * The instance execution end time
+ */
+ private Date endTime;
+
+ /**
+ * The cause when failed
+ */
+ private String cause;
+
+ /**
+ * The created time
+ */
+ private Date ctime;
+
+ /**
+ * The updated time
+ */
+ private Date utime;
+
+ @Override
+ public Long getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(Long jobId) {
+ this.jobId = jobId;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public String getServer() {
+ return server;
+ }
+
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ public Integer getMaxShardPullCount() {
+ return maxShardPullCount;
+ }
+
+ public void setMaxShardPullCount(Integer maxShardPullCount) {
+ this.maxShardPullCount = maxShardPullCount;
+ }
+
+ public String getJobParam() {
+ return jobParam;
+ }
+
+ public void setJobParam(String jobParam) {
+ this.jobParam = jobParam;
+ }
+
+ public Integer getTotalShardCount() {
+ return totalShardCount;
+ }
+
+ public void setTotalShardCount(Integer totalShardCount) {
+ this.totalShardCount = totalShardCount;
+ }
+
+ public Date getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+
+ public Date getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(Date endTime) {
+ this.endTime = endTime;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public void setCause(String cause) {
+ this.cause = cause;
+ }
+
+ public Date getCtime() {
+ return ctime;
+ }
+
+ @Override
+ public void setCtime(Date ctime) {
+ this.ctime = ctime;
+ }
+
+ public Date getUtime() {
+ return utime;
+ }
+
+ @Override
+ public void setUtime(Date utime) {
+ this.utime = utime;
+ }
+
+ @Override
+ public String toString() {
+ return "JobInstance{" +
+ "id=" + id +
+ ", jobId=" + jobId +
+ ", status=" + status +
+ ", server='" + server + '\'' +
+ ", maxShardPullCount=" + maxShardPullCount +
+ ", jobParam='" + jobParam + '\'' +
+ ", totalShardCount=" + totalShardCount +
+ ", startTime=" + startTime +
+ ", endTime=" + endTime +
+ ", cause='" + cause + '\'' +
+ ", ctime=" + ctime +
+ ", utime=" + utime +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/JobInstanceShard.java b/antares-common/src/main/java/me/hao0/antares/common/model/JobInstanceShard.java
new file mode 100644
index 00000000..8045e7e6
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/JobInstanceShard.java
@@ -0,0 +1,211 @@
+package me.hao0.antares.common.model;
+
+import me.hao0.antares.common.anno.RedisModel;
+import java.util.Date;
+
+/**
+ * The job instance shard
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+@RedisModel(prefix = "job_ins_sds")
+public class JobInstanceShard implements Model {
+
+ private static final long serialVersionUID = 4699655089712303564L;
+
+ /**
+ * The primary key
+ */
+ private Long id;
+
+ /**
+ * The job instance id
+ */
+ private Long instanceId;
+
+ /**
+ * The shard item index
+ */
+ private Integer item;
+
+ /**
+ * The shard param
+ */
+ private String param;
+
+ /**
+ * The pull client
+ */
+ private String pullClient;
+
+ /**
+ * The finish client
+ */
+ private String finishClient;
+
+ /**
+ * The status
+ */
+ private Integer status;
+
+ /**
+ * The cause when failed
+ */
+ private String cause;
+
+ /**
+ * The pullClient pull time;
+ */
+ private Date pullTime;
+
+ /**
+ * The shard is pulled by total count:
+ *
+ * 1 : normal
+ * >1 : has failed execution
+ */
+ private Integer pullCount;
+
+ /**
+ * The pullClient executing start time
+ */
+ private Date startTime;
+
+ /**
+ * The pullClient executing end time
+ */
+ private Date endTime;
+
+ private Date ctime;
+
+ @Override
+ public Long getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getInstanceId() {
+ return instanceId;
+ }
+
+ public void setInstanceId(Long instanceId) {
+ this.instanceId = instanceId;
+ }
+
+ public Integer getItem() {
+ return item;
+ }
+
+ public void setItem(Integer item) {
+ this.item = item;
+ }
+
+ public String getParam() {
+ return param;
+ }
+
+ public void setParam(String param) {
+ this.param = param;
+ }
+
+ public String getPullClient() {
+ return pullClient;
+ }
+
+ public void setPullClient(String pullClient) {
+ this.pullClient = pullClient;
+ }
+
+ public String getFinishClient() {
+ return finishClient;
+ }
+
+ public void setFinishClient(String finishClient) {
+ this.finishClient = finishClient;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public void setCause(String cause) {
+ this.cause = cause;
+ }
+
+ public Date getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+
+ public Date getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(Date endTime) {
+ this.endTime = endTime;
+ }
+
+ public Date getCtime() {
+ return ctime;
+ }
+
+ @Override
+ public void setCtime(Date ctime) {
+ this.ctime = ctime;
+ }
+
+ @Override
+ public void setUtime(Date utime) {
+ // ignore
+ }
+
+ public Date getPullTime() {
+ return pullTime;
+ }
+
+ public void setPullTime(Date pullTime) {
+ this.pullTime = pullTime;
+ }
+
+ public Integer getPullCount() {
+ return pullCount;
+ }
+
+ public void setPullCount(Integer pullCount) {
+ this.pullCount = pullCount;
+ }
+
+ @Override
+ public String toString() {
+ return "JobInstanceShard{" +
+ "id=" + id +
+ ", instanceId=" + instanceId +
+ ", item=" + item +
+ ", param='" + param + '\'' +
+ ", pullClient='" + pullClient + '\'' +
+ ", finishClient='" + finishClient + '\'' +
+ ", status=" + status +
+ ", cause='" + cause + '\'' +
+ ", pullTime=" + pullTime +
+ ", pullCount=" + pullCount +
+ ", startTime=" + startTime +
+ ", endTime=" + endTime +
+ ", ctime=" + ctime +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/JobServer.java b/antares-common/src/main/java/me/hao0/antares/common/model/JobServer.java
new file mode 100644
index 00000000..d14ef7f8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/JobServer.java
@@ -0,0 +1,51 @@
+package me.hao0.antares.common.model;
+
+import me.hao0.antares.common.anno.RedisModel;
+
+import java.io.Serializable;
+
+/**
+ * The job server relation
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+@RedisModel(prefix = "job_srs")
+public class JobServer implements Serializable {
+
+ private static final long serialVersionUID = -5081467017356824898L;
+
+ private Long jobId;
+
+ private String server;
+
+ public JobServer(){}
+
+ public JobServer(Long jobId, String server) {
+ this.jobId = jobId;
+ this.server = server;
+ }
+
+ public Long getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(Long jobId) {
+ this.jobId = jobId;
+ }
+
+ public String getServer() {
+ return server;
+ }
+
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ @Override
+ public String toString() {
+ return "JobServer{" +
+ "jobId=" + jobId +
+ ", server='" + server + '\'' +
+ '}';
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/Model.java b/antares-common/src/main/java/me/hao0/antares/common/model/Model.java
new file mode 100644
index 00000000..099d8f33
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/Model.java
@@ -0,0 +1,20 @@
+package me.hao0.antares.common.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * The class implements this interface will be persist to the storage
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public interface Model extends Serializable {
+
+ K getId();
+
+ void setId(K id);
+
+ void setCtime(Date ctime);
+
+ void setUtime(Date utime);
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/enums/FinishShardStatus.java b/antares-common/src/main/java/me/hao0/antares/common/model/enums/FinishShardStatus.java
new file mode 100644
index 00000000..33f13524
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/enums/FinishShardStatus.java
@@ -0,0 +1,70 @@
+package me.hao0.antares.common.model.enums;
+
+import java.util.Objects;
+
+/**
+ * Pull shard staths
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public enum FinishShardStatus {
+
+ /**
+ * The job instance doesn't exist
+ */
+ INSTANCE_NOT_EXIST(0),
+
+ /**
+ * The job instance has finished
+ */
+ INSTANCE_FINISH(1),
+
+ /**
+ * Is not the shard puller
+ */
+ NOT_OWNER(2),
+
+ /**
+ * Pull a shard successfully
+ */
+ FINISH_SUCCESS(3),
+
+ /**
+ * Pull failed
+ */
+ FINISH_FAILED(4),
+
+ /**
+ * The shard not exist
+ */
+ SHARD_NOT_EXIST(6);
+
+ private Integer value;
+
+ FinishShardStatus(Integer value){
+ this.value = value;
+ }
+
+ public Integer value(){
+ return value;
+ }
+
+ public static FinishShardStatus from(Integer value){
+ for (FinishShardStatus s : FinishShardStatus.values()){
+ if (Objects.equals(s.value, value)){
+ return s;
+ }
+ }
+ throw new IllegalStateException("invalid pull shard status value: " + value);
+ }
+
+ /**
+ * Need finish again
+ * @param value the pull shard status value
+ * @return return true if pull again, or false
+ */
+ public static Boolean needFinish(Integer value){
+ FinishShardStatus status = from(value);
+ return status == FINISH_FAILED;
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobInstanceShardStatus.java b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobInstanceShardStatus.java
new file mode 100644
index 00000000..e86c654e
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobInstanceShardStatus.java
@@ -0,0 +1,56 @@
+package me.hao0.antares.common.model.enums;
+
+import java.util.Objects;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public enum JobInstanceShardStatus {
+
+ /**
+ * Created
+ */
+ NEW(0, "job.instance.shard.status.new"),
+
+ /**
+ * Pulled
+ */
+ RUNNING(1, "job.instance.shard.status.running"),
+
+ /**
+ * Success
+ */
+ SUCCESS(2, "job.instance.shard.status.success"),
+
+ /**
+ * Failure
+ */
+ FAILED(3, "job.instance.shard.status.failed");
+
+ private Integer value;
+
+ private String code;
+
+ JobInstanceShardStatus(Integer value, String code){
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer value(){
+ return value;
+ }
+
+ public String code(){
+ return code;
+ }
+
+ public static JobInstanceShardStatus from(Integer status){
+ for (JobInstanceShardStatus s : JobInstanceShardStatus.values()){
+ if (Objects.equals(s.value, status)){
+ return s;
+ }
+ }
+ throw new IllegalStateException("invalid job instance shard status value: " + status);
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobInstanceStatus.java b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobInstanceStatus.java
new file mode 100644
index 00000000..0dd2d862
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobInstanceStatus.java
@@ -0,0 +1,68 @@
+package me.hao0.antares.common.model.enums;
+
+import java.util.Objects;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public enum JobInstanceStatus {
+
+ /**
+ * The job instance is created
+ */
+ NEW(1, "job.instance.status.new"),
+
+ /**
+ * The job instance is running
+ */
+ RUNNING(2, "job.instance.status.running"),
+
+ /**
+ * The job instance executed successfully
+ */
+ SUCCESS(3, "job.instance.status.success"),
+
+ /**
+ * The job instance executed failed
+ */
+ FAILED(4, "job.instance.status.failed"),
+
+ /**
+ * The job instance is forced to be terminated
+ */
+ TERMINATED(5, "job.instance.status.terminated");
+
+ private Integer value;
+
+ private String code;
+
+ JobInstanceStatus(Integer value, String code){
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer value(){
+ return value;
+ }
+
+ public String code(){
+ return code;
+ }
+
+ public static JobInstanceStatus from(Integer status){
+ for (JobInstanceStatus s : JobInstanceStatus.values()){
+ if (Objects.equals(s.value, status)){
+ return s;
+ }
+ }
+ throw new IllegalStateException("invalid job instance status value: " + status);
+ }
+
+ public static boolean isFinal(Integer status) {
+ JobInstanceStatus instanceStatus = from(status);
+ return instanceStatus == SUCCESS
+ || instanceStatus == FAILED
+ || instanceStatus == TERMINATED;
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobState.java b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobState.java
new file mode 100644
index 00000000..d94851e8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobState.java
@@ -0,0 +1,74 @@
+package me.hao0.antares.common.model.enums;
+
+import java.util.Objects;
+
+/**
+ * The job running state
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public enum JobState {
+
+ /**
+ * Disable
+ */
+ DISABLE(0, "job.state.disable"),
+
+ /**
+ * There are no job instances
+ */
+ WAITING(1, "job.state.waiting"),
+
+ /**
+ * There are job instances
+ */
+ RUNNING(2, "job.state.running"),
+
+ /**
+ * Enable, but no scheduler
+ */
+ STOPPED(3, "job.state.stopped"),
+
+ /**
+ * Failed to execute the latest job instance
+ */
+ FAILED(4, "job.state.failed"),
+
+ /**
+ * The job is paused
+ */
+ PAUSED(5, "job.state.paused");
+
+ private Integer value;
+
+ private String code;
+
+ JobState(Integer value, String code){
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer value(){
+ return value;
+ }
+
+ public String code(){
+ return code;
+ }
+
+ public static JobState from(Integer state){
+ for (JobState s : JobState.values()){
+ if (Objects.equals(s.value, state)){
+ return s;
+ }
+ }
+ throw new IllegalStateException("invalid job state value: " + state);
+ }
+
+ public static Boolean isScheduling(JobState state) {
+ return state == WAITING
+ || state == RUNNING
+ || state == PAUSED
+ || state == FAILED;
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobStatus.java b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobStatus.java
new file mode 100644
index 00000000..a4a0dbc2
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobStatus.java
@@ -0,0 +1,39 @@
+package me.hao0.antares.common.model.enums;
+
+import java.util.Objects;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public enum JobStatus {
+
+ /**
+ * Disable the job
+ */
+ DISABLE(0),
+
+ /**
+ * Enable the job
+ */
+ ENABLE(1);
+
+ private Integer value;
+
+ JobStatus(Integer value){
+ this.value = value;
+ }
+
+ public Integer value(){
+ return value;
+ }
+
+ public static JobStatus from(Integer status){
+ for (JobStatus s : JobStatus.values()){
+ if (Objects.equals(s.value, status)){
+ return s;
+ }
+ }
+ throw new IllegalStateException("invalid job status value: " + status);
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobType.java b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobType.java
new file mode 100644
index 00000000..6feb1a9c
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/enums/JobType.java
@@ -0,0 +1,41 @@
+package me.hao0.antares.common.model.enums;
+
+import java.util.Objects;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public enum JobType {
+
+ /**
+ * Default Timer job
+ */
+ DEFAULT(1),
+
+ /**
+ * Http callback job
+ */
+ HTTP(2);
+
+ private Integer value;
+
+ JobType(Integer value){
+ this.value = value;
+ }
+
+ public Integer value(){
+ return value;
+ }
+
+ public static JobType from(Integer value){
+
+ for (JobType t : JobType.values()){
+ if (Objects.equals(t.value, value)){
+ return t;
+ }
+ }
+
+ throw new IllegalStateException("invalid job type value: " + value);
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/model/enums/ShardOperateRespCode.java b/antares-common/src/main/java/me/hao0/antares/common/model/enums/ShardOperateRespCode.java
new file mode 100644
index 00000000..dc584a82
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/model/enums/ShardOperateRespCode.java
@@ -0,0 +1,143 @@
+package me.hao0.antares.common.model.enums;
+
+import java.util.Objects;
+
+/**
+ * Pull shard staths
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public enum ShardOperateRespCode {
+
+ /**
+ * The job instance doesn't exist
+ */
+ INSTANCE_NOT_EXIST(0),
+
+ /**
+ * The job instance has finished
+ */
+ INSTANCE_FINISH(1),
+
+
+ /******* FOR SHARD PULL *******/
+ /**
+ * All shards are pulled
+ */
+ SHARD_NO_AVAILABLE(2),
+
+ /**
+ * Pull a shard successfully
+ */
+ SHARD_PULL_SUCCESS(3),
+
+ /**
+ * Pull failed
+ */
+ SHARD_PULL_FAILED(4),
+
+ /**
+ * The shard pull count exceeds max pull count
+ */
+ SHARD_PULL_COUNT_EXCEED(5),
+
+ /******* FOR SHARD FINISH *******/
+ /**
+ * Is not the shard puller when push back
+ */
+ SHARD_NOT_PULLER(6),
+
+ /**
+ * Finish success
+ */
+ SHARD_FINISH_SUCCESS(7),
+
+ /**
+ * Finish fail
+ */
+ SHARD_FINISH_FAILED(8),
+
+ /**
+ * The shard not exist
+ */
+ SHARD_NOT_EXIST(9),
+
+ /**
+ * The shard create fail
+ */
+ SHARD_CREATE_FAILED(10),
+
+ /**
+ * The shard status is final
+ */
+ SHARD_FINAL(11),
+
+ /******* FOR SHARD PUSH *******/
+
+ /**
+ * Return the shard successfully
+ */
+ SHARD_RETURN_SUCCESS(12),
+
+ /**
+ * Return the shard failed
+ */
+ SHARD_RETURN_FAILED(13);
+
+ private Integer value;
+
+ ShardOperateRespCode(Integer value){
+ this.value = value;
+ }
+
+ public Integer value(){
+ return value;
+ }
+
+ public static ShardOperateRespCode from(Integer value){
+ for (ShardOperateRespCode s : ShardOperateRespCode.values()){
+ if (Objects.equals(s.value, value)){
+ return s;
+ }
+ }
+ throw new IllegalStateException("invalid pull shard status value: " + value);
+ }
+
+ /**
+ * Need pull again
+ * @param code the response code
+ * @return return true if pull again, or false
+ */
+ public static Boolean needPullAgain(ShardOperateRespCode code){
+
+ return (code == SHARD_NO_AVAILABLE
+ || code == SHARD_PULL_FAILED);
+ }
+
+ /**
+ * Need finish again
+ * @param code the response code
+ * @return return true if pull again, or false
+ */
+ public static Boolean needFinishAgain(ShardOperateRespCode code){
+ return code == SHARD_FINISH_FAILED;
+ }
+
+ /**
+ * Need return again
+ * @param code the response code
+ * @return return true if push again, or false
+ */
+ public static Boolean needReturnAgain(ShardOperateRespCode code){
+ return code == SHARD_RETURN_FAILED;
+ }
+
+ /**
+ * Need clean the job instance
+ * @param code the shard operate response code
+ * @return return true if need clean job instance, thinking that the dirty job instance data in zk
+ */
+ public static Boolean needCleanJobInstance(ShardOperateRespCode code) {
+ return code == INSTANCE_FINISH || code == INSTANCE_NOT_EXIST;
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/Attempt.java b/antares-common/src/main/java/me/hao0/antares/common/retry/Attempt.java
new file mode 100644
index 00000000..d02ec1c1
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/Attempt.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * An attempt of a call, which resulted either in a result returned by the call,
+ * or in a Throwable thrown by the call.
+ *
+ * @param The type returned by the wrapped callable.
+ * @author JB
+ */
+public interface Attempt {
+
+ /**
+ * Returns the result of the attempt, if any.
+ *
+ * @return the result of the attempt
+ * @throws ExecutionException if an exception was thrown by the attempt. The thrown
+ * exception is set as the cause of the ExecutionException
+ */
+ public V get() throws ExecutionException;
+
+ /**
+ * Tells if the call returned a result or not
+ *
+ * @return true
if the call returned a result, false
+ * if it threw an exception
+ */
+ public boolean hasResult();
+
+ /**
+ * Tells if the call threw an exception or not
+ *
+ * @return true
if the call threw an exception, false
+ * if it returned a result
+ */
+ public boolean hasException();
+
+ /**
+ * Gets the result of the call
+ *
+ * @return the result of the call
+ * @throws IllegalStateException if the call didn't return a result, but threw an exception,
+ * as indicated by {@link #hasResult()}
+ */
+ public V getResult() throws IllegalStateException;
+
+ /**
+ * Gets the exception thrown by the call
+ *
+ * @return the exception thrown by the call
+ * @throws IllegalStateException if the call didn't throw an exception,
+ * as indicated by {@link #hasException()}
+ */
+ public Throwable getExceptionCause() throws IllegalStateException;
+
+ /**
+ * The number, starting from 1, of this attempt.
+ *
+ * @return the attempt number
+ */
+ public long getAttemptNumber();
+
+ /**
+ * The delay since the start of the first attempt, in milliseconds.
+ *
+ * @return the delay since the start of the first attempt, in milliseconds
+ */
+ public long getDelaySinceFirstAttempt();
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/AttemptTimeLimiter.java b/antares-common/src/main/java/me/hao0/antares/common/retry/AttemptTimeLimiter.java
new file mode 100644
index 00000000..5e8f5789
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/AttemptTimeLimiter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A rule to wrap any single attempt in a time limit, where it will possibly be interrupted if the limit is exceeded.
+ *
+ * @param return type of Callable
+ * @author Jason Dunkelberger (dirkraft)
+ */
+public interface AttemptTimeLimiter {
+ /**
+ * @param callable to subject to the time limit
+ * @return the return of the given callable
+ * @throws Exception any exception from this invocation
+ */
+ V call(Callable callable) throws Exception;
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/AttemptTimeLimiters.java b/antares-common/src/main/java/me/hao0/antares/common/retry/AttemptTimeLimiters.java
new file mode 100644
index 00000000..73fdbd3f
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/AttemptTimeLimiters.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.SimpleTimeLimiter;
+import com.google.common.util.concurrent.TimeLimiter;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Factory class for instances of {@link AttemptTimeLimiter}
+ *
+ * @author Jason Dunkelberger (dirkraft)
+ */
+public class AttemptTimeLimiters {
+
+ private AttemptTimeLimiters() {
+ }
+
+ /**
+ * @param The type of the computation result.
+ * @return an {@link AttemptTimeLimiter} impl which has no time limit
+ */
+ public static AttemptTimeLimiter noTimeLimit() {
+ return new NoAttemptTimeLimit();
+ }
+
+ /**
+ * For control over thread management, it is preferable to offer an {@link ExecutorService} through the other
+ * factory method, {@link #fixedTimeLimit(long, TimeUnit, ExecutorService)}. See the note on
+ * {@link SimpleTimeLimiter#SimpleTimeLimiter(ExecutorService)}, which this AttemptTimeLimiter uses.
+ *
+ * @param duration that an attempt may persist before being circumvented
+ * @param timeUnit of the 'duration' arg
+ * @param the type of the computation result
+ * @return an {@link AttemptTimeLimiter} with a fixed time limit for each attempt
+ */
+ public static AttemptTimeLimiter fixedTimeLimit(long duration, @Nonnull TimeUnit timeUnit) {
+ Preconditions.checkNotNull(timeUnit);
+ return new FixedAttemptTimeLimit(duration, timeUnit);
+ }
+
+ /**
+ * @param duration that an attempt may persist before being circumvented
+ * @param timeUnit of the 'duration' arg
+ * @param executorService used to enforce time limit
+ * @param the type of the computation result
+ * @return an {@link AttemptTimeLimiter} with a fixed time limit for each attempt
+ */
+ public static AttemptTimeLimiter fixedTimeLimit(long duration, @Nonnull TimeUnit timeUnit, @Nonnull ExecutorService executorService) {
+ Preconditions.checkNotNull(timeUnit);
+ return new FixedAttemptTimeLimit(duration, timeUnit, executorService);
+ }
+
+ @Immutable
+ private static final class NoAttemptTimeLimit implements AttemptTimeLimiter {
+ @Override
+ public V call(Callable callable) throws Exception {
+ return callable.call();
+ }
+ }
+
+ @Immutable
+ private static final class FixedAttemptTimeLimit implements AttemptTimeLimiter {
+
+ private final TimeLimiter timeLimiter;
+ private final long duration;
+ private final TimeUnit timeUnit;
+
+ public FixedAttemptTimeLimit(long duration, @Nonnull TimeUnit timeUnit) {
+ this(new SimpleTimeLimiter(), duration, timeUnit);
+ }
+
+ public FixedAttemptTimeLimit(long duration, @Nonnull TimeUnit timeUnit, @Nonnull ExecutorService executorService) {
+ this(new SimpleTimeLimiter(executorService), duration, timeUnit);
+ }
+
+ private FixedAttemptTimeLimit(@Nonnull TimeLimiter timeLimiter, long duration, @Nonnull TimeUnit timeUnit) {
+ Preconditions.checkNotNull(timeLimiter);
+ Preconditions.checkNotNull(timeUnit);
+ this.timeLimiter = timeLimiter;
+ this.duration = duration;
+ this.timeUnit = timeUnit;
+ }
+
+ @Override
+ public V call(Callable callable) throws Exception {
+ return timeLimiter.callWithTimeout(callable, duration, timeUnit, true);
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/BlockStrategies.java b/antares-common/src/main/java/me/hao0/antares/common/retry/BlockStrategies.java
new file mode 100644
index 00000000..d1144496
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/BlockStrategies.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Factory class for {@link BlockStrategy} instances.
+ */
+public final class BlockStrategies {
+
+ private static final BlockStrategy THREAD_SLEEP_STRATEGY = new ThreadSleepStrategy();
+
+ private BlockStrategies() {
+ }
+
+ /**
+ * Returns a block strategy that puts the current thread to sleep between
+ * retries.
+ *
+ * @return a block strategy that puts the current thread to sleep between retries
+ */
+ public static BlockStrategy threadSleepStrategy() {
+ return THREAD_SLEEP_STRATEGY;
+ }
+
+ @Immutable
+ private static class ThreadSleepStrategy implements BlockStrategy {
+
+ @Override
+ public void block(long sleepTime) throws InterruptedException {
+ Thread.sleep(sleepTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/BlockStrategy.java b/antares-common/src/main/java/me/hao0/antares/common/retry/BlockStrategy.java
new file mode 100644
index 00000000..4ec8c991
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/BlockStrategy.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+/**
+ * This is a strategy used to decide how a retryer should block between retry
+ * attempts. Normally this is just a Thread.sleep(), but implementations can be
+ * something more elaborate if desired.
+ */
+public interface BlockStrategy {
+
+ /**
+ * Attempt to block for the designated amount of time. Implementations
+ * that don't block or otherwise delay the processing from within this
+ * method for the given sleep duration can significantly modify the behavior
+ * of any configured {@link com.github.rholder.retry.WaitStrategy}. Caution
+ * is advised when generating your own implementations.
+ *
+ * @param sleepTime the computed sleep duration in milliseconds
+ * @throws InterruptedException
+ */
+ void block(long sleepTime) throws InterruptedException;
+}
\ No newline at end of file
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/DefaultRetryListener.java b/antares-common/src/main/java/me/hao0/antares/common/retry/DefaultRetryListener.java
new file mode 100644
index 00000000..3a08a8c8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/DefaultRetryListener.java
@@ -0,0 +1,30 @@
+package me.hao0.antares.common.retry;
+
+import com.google.common.base.Throwables;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class DefaultRetryListener implements RetryListener {
+
+ private static final Logger log = LoggerFactory.getLogger(DefaultRetryListener.class);
+
+ @Override
+ public void onRetry(Attempt attempt) {
+ try {
+ long attemptTimes = attempt.getAttemptNumber();
+ if (attemptTimes > 2 && attemptTimes < 10){
+ log.info("try the {} times, and result is: {}", attempt.getAttemptNumber(), attempt.get());
+ } else if(attemptTimes > 10) {
+ log.warn("try the {} times, and result is: {}", attempt.getAttemptNumber(), attempt.get());
+ }
+ } catch (ExecutionException e) {
+ // ignore
+ log.error("failed to on retry, cause: {}", Throwables.getStackTraceAsString(e));
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/RetryException.java b/antares-common/src/main/java/me/hao0/antares/common/retry/RetryException.java
new file mode 100644
index 00000000..2e53b8d2
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/RetryException.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An exception indicating that none of the attempts of the {@link Retryer}
+ * succeeded. If the last {@link Attempt} resulted in an Exception, it is set as
+ * the cause of the {@link RetryException}.
+ *
+ * @author JB
+ */
+@Immutable
+public final class RetryException extends Exception {
+
+ private final int numberOfFailedAttempts;
+ private final Attempt> lastFailedAttempt;
+
+ /**
+ * If the last {@link Attempt} had an Exception, ensure it is available in
+ * the stack trace.
+ *
+ * @param numberOfFailedAttempts times we've tried and failed
+ * @param lastFailedAttempt what happened the last time we failed
+ */
+ public RetryException(int numberOfFailedAttempts, @Nonnull Attempt> lastFailedAttempt) {
+ this("Retrying failed to complete successfully after " + numberOfFailedAttempts + " attempts.", numberOfFailedAttempts, lastFailedAttempt);
+ }
+
+ /**
+ * If the last {@link Attempt} had an Exception, ensure it is available in
+ * the stack trace.
+ *
+ * @param message Exception description to be added to the stack trace
+ * @param numberOfFailedAttempts times we've tried and failed
+ * @param lastFailedAttempt what happened the last time we failed
+ */
+ public RetryException(String message, int numberOfFailedAttempts, Attempt> lastFailedAttempt) {
+ super(message, checkNotNull(lastFailedAttempt, "Last attempt was null").hasException() ? lastFailedAttempt.getExceptionCause() : null);
+ this.numberOfFailedAttempts = numberOfFailedAttempts;
+ this.lastFailedAttempt = lastFailedAttempt;
+ }
+
+ /**
+ * Returns the number of failed attempts
+ *
+ * @return the number of failed attempts
+ */
+ public int getNumberOfFailedAttempts() {
+ return numberOfFailedAttempts;
+ }
+
+ /**
+ * Returns the last failed attempt
+ *
+ * @return the last failed attempt
+ */
+ public Attempt> getLastFailedAttempt() {
+ return lastFailedAttempt;
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/RetryListener.java b/antares-common/src/main/java/me/hao0/antares/common/retry/RetryListener.java
new file mode 100644
index 00000000..eaea77e8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/RetryListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * This listener provides callbacks for several events that occur when running
+ * code through a {@link Retryer} instance.
+ */
+@Beta
+public interface RetryListener {
+
+ /**
+ * This method with fire no matter what the result is and before the
+ * rejection predicate and stop strategies are applied.
+ *
+ * @param attempt the current {@link Attempt}
+ * @param the type returned by the retryer callable
+ */
+ void onRetry(Attempt attempt);
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/Retryer.java b/antares-common/src/main/java/me/hao0/antares/common/retry/Retryer.java
new file mode 100644
index 00000000..c4624432
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/Retryer.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A retryer, which executes a call, and retries it until it succeeds, or
+ * a stop strategy decides to stop retrying. A wait strategy is used to sleep
+ * between attempts. The strategy to decide if the call succeeds or not is
+ * also configurable.
+ *
+ * A retryer can also wrap the callable into a RetryerCallable, which can be submitted to an executor.
+ *
+ * Retryer instances are better constructed with a {@link RetryerBuilder}. A retryer
+ * is thread-safe, provided the arguments passed to its constructor are thread-safe.
+ *
+ * @param the type of the call return value
+ * @author JB
+ * @author Jason Dunkelberger (dirkraft)
+ */
+public final class Retryer {
+ private final StopStrategy stopStrategy;
+ private final WaitStrategy waitStrategy;
+ private final BlockStrategy blockStrategy;
+ private final AttemptTimeLimiter attemptTimeLimiter;
+ private final Predicate> rejectionPredicate;
+ private final Collection listeners;
+
+ /**
+ * Constructor
+ *
+ * @param stopStrategy the strategy used to decide when the retryer must stop retrying
+ * @param waitStrategy the strategy used to decide how much time to sleep between attempts
+ * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
+ * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
+ * strategy indicates otherwise or the thread is interrupted.
+ */
+ public Retryer(@Nonnull StopStrategy stopStrategy,
+ @Nonnull WaitStrategy waitStrategy,
+ @Nonnull Predicate> rejectionPredicate) {
+
+ this(AttemptTimeLimiters.noTimeLimit(), stopStrategy, waitStrategy, BlockStrategies.threadSleepStrategy(), rejectionPredicate);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param attemptTimeLimiter to prevent from any single attempt from spinning infinitely
+ * @param stopStrategy the strategy used to decide when the retryer must stop retrying
+ * @param waitStrategy the strategy used to decide how much time to sleep between attempts
+ * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
+ * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
+ * strategy indicates otherwise or the thread is interrupted.
+ */
+ public Retryer(@Nonnull AttemptTimeLimiter attemptTimeLimiter,
+ @Nonnull StopStrategy stopStrategy,
+ @Nonnull WaitStrategy waitStrategy,
+ @Nonnull Predicate> rejectionPredicate) {
+ this(attemptTimeLimiter, stopStrategy, waitStrategy, BlockStrategies.threadSleepStrategy(), rejectionPredicate);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param attemptTimeLimiter to prevent from any single attempt from spinning infinitely
+ * @param stopStrategy the strategy used to decide when the retryer must stop retrying
+ * @param waitStrategy the strategy used to decide how much time to sleep between attempts
+ * @param blockStrategy the strategy used to decide how to block between retry attempts; eg, Thread#sleep(), latches, etc.
+ * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
+ * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
+ * strategy indicates otherwise or the thread is interrupted.
+ */
+ public Retryer(@Nonnull AttemptTimeLimiter attemptTimeLimiter,
+ @Nonnull StopStrategy stopStrategy,
+ @Nonnull WaitStrategy waitStrategy,
+ @Nonnull BlockStrategy blockStrategy,
+ @Nonnull Predicate> rejectionPredicate) {
+ this(attemptTimeLimiter, stopStrategy, waitStrategy, blockStrategy, rejectionPredicate, new ArrayList());
+ }
+
+ /**
+ * Constructor
+ *
+ * @param attemptTimeLimiter to prevent from any single attempt from spinning infinitely
+ * @param stopStrategy the strategy used to decide when the retryer must stop retrying
+ * @param waitStrategy the strategy used to decide how much time to sleep between attempts
+ * @param blockStrategy the strategy used to decide how to block between retry attempts; eg, Thread#sleep(), latches, etc.
+ * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
+ * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
+ * strategy indicates otherwise or the thread is interrupted.
+ * @param listeners collection of retry listeners
+ */
+ @Beta
+ public Retryer(@Nonnull AttemptTimeLimiter attemptTimeLimiter,
+ @Nonnull StopStrategy stopStrategy,
+ @Nonnull WaitStrategy waitStrategy,
+ @Nonnull BlockStrategy blockStrategy,
+ @Nonnull Predicate> rejectionPredicate,
+ @Nonnull Collection listeners) {
+ Preconditions.checkNotNull(attemptTimeLimiter, "timeLimiter may not be null");
+ Preconditions.checkNotNull(stopStrategy, "stopStrategy may not be null");
+ Preconditions.checkNotNull(waitStrategy, "waitStrategy may not be null");
+ Preconditions.checkNotNull(blockStrategy, "blockStrategy may not be null");
+ Preconditions.checkNotNull(rejectionPredicate, "rejectionPredicate may not be null");
+ Preconditions.checkNotNull(listeners, "listeners may not null");
+
+ this.attemptTimeLimiter = attemptTimeLimiter;
+ this.stopStrategy = stopStrategy;
+ this.waitStrategy = waitStrategy;
+ this.blockStrategy = blockStrategy;
+ this.rejectionPredicate = rejectionPredicate;
+ this.listeners = listeners;
+ }
+
+ /**
+ * Executes the given callable. If the rejection predicate
+ * accepts the attempt, the stop strategy is used to decide if a new attempt
+ * must be made. Then the wait strategy is used to decide how much time to sleep
+ * and a new attempt is made.
+ *
+ * @param callable the callable task to be executed
+ * @return the computed result of the given callable
+ * @throws ExecutionException if the given callable throws an exception, and the
+ * rejection predicate considers the attempt as successful. The original exception
+ * is wrapped into an ExecutionException.
+ * @throws RetryException if all the attempts failed before the stop strategy decided
+ * to abort, or the thread was interrupted. Note that if the thread is interrupted,
+ * this exception is thrown and the thread's interrupt status is set.
+ */
+ public V call(Callable callable) throws ExecutionException, RetryException {
+ long startTime = System.nanoTime();
+ for (int attemptNumber = 1; ; attemptNumber++) {
+ Attempt attempt;
+ try {
+ V result = attemptTimeLimiter.call(callable);
+ attempt = new ResultAttempt(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
+ } catch (Throwable t) {
+ attempt = new ExceptionAttempt(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
+ }
+
+ for (RetryListener listener : listeners) {
+ listener.onRetry(attempt);
+ }
+
+ if (!rejectionPredicate.apply(attempt)) {
+ return attempt.get();
+ }
+ if (stopStrategy.shouldStop(attempt)) {
+ throw new RetryException(attemptNumber, attempt);
+ } else {
+ long sleepTime = waitStrategy.computeSleepTime(attempt);
+ try {
+ blockStrategy.block(sleepTime);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RetryException(attemptNumber, attempt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Wraps the given {@link Callable} in a {@link RetryerCallable}, which can
+ * be submitted to an executor. The returned {@link RetryerCallable} uses
+ * this {@link Retryer} instance to call the given {@link Callable}.
+ *
+ * @param callable the callable to wrap
+ * @return a {@link RetryerCallable} that behaves like the given {@link Callable} with retry behavior defined by this {@link Retryer}
+ */
+ public RetryerCallable wrap(Callable callable) {
+ return new RetryerCallable(this, callable);
+ }
+
+ @Immutable
+ static final class ResultAttempt implements Attempt {
+ private final R result;
+ private final long attemptNumber;
+ private final long delaySinceFirstAttempt;
+
+ public ResultAttempt(R result, long attemptNumber, long delaySinceFirstAttempt) {
+ this.result = result;
+ this.attemptNumber = attemptNumber;
+ this.delaySinceFirstAttempt = delaySinceFirstAttempt;
+ }
+
+ @Override
+ public R get() throws ExecutionException {
+ return result;
+ }
+
+ @Override
+ public boolean hasResult() {
+ return true;
+ }
+
+ @Override
+ public boolean hasException() {
+ return false;
+ }
+
+ @Override
+ public R getResult() throws IllegalStateException {
+ return result;
+ }
+
+ @Override
+ public Throwable getExceptionCause() throws IllegalStateException {
+ throw new IllegalStateException("The attempt resulted in a result, not in an exception");
+ }
+
+ @Override
+ public long getAttemptNumber() {
+ return attemptNumber;
+ }
+
+ @Override
+ public long getDelaySinceFirstAttempt() {
+ return delaySinceFirstAttempt;
+ }
+ }
+
+ @Immutable
+ static final class ExceptionAttempt implements Attempt {
+ private final ExecutionException e;
+ private final long attemptNumber;
+ private final long delaySinceFirstAttempt;
+
+ public ExceptionAttempt(Throwable cause, long attemptNumber, long delaySinceFirstAttempt) {
+ this.e = new ExecutionException(cause);
+ this.attemptNumber = attemptNumber;
+ this.delaySinceFirstAttempt = delaySinceFirstAttempt;
+ }
+
+ @Override
+ public R get() throws ExecutionException {
+ throw e;
+ }
+
+ @Override
+ public boolean hasResult() {
+ return false;
+ }
+
+ @Override
+ public boolean hasException() {
+ return true;
+ }
+
+ @Override
+ public R getResult() throws IllegalStateException {
+ throw new IllegalStateException("The attempt resulted in an exception, not in a result");
+ }
+
+ @Override
+ public Throwable getExceptionCause() throws IllegalStateException {
+ return e.getCause();
+ }
+
+ @Override
+ public long getAttemptNumber() {
+ return attemptNumber;
+ }
+
+ @Override
+ public long getDelaySinceFirstAttempt() {
+ return delaySinceFirstAttempt;
+ }
+ }
+
+ /**
+ * A {@link Callable} which wraps another {@link Callable} in order to add
+ * retrying behavior from a given {@link Retryer} instance.
+ *
+ * @author JB
+ */
+ public static class RetryerCallable implements Callable {
+ private Retryer retryer;
+ private Callable callable;
+
+ private RetryerCallable(Retryer retryer,
+ Callable callable) {
+ this.retryer = retryer;
+ this.callable = callable;
+ }
+
+ /**
+ * Makes the enclosing retryer call the wrapped callable.
+ *
+ * @see Retryer#call(Callable)
+ */
+ @Override
+ public X call() throws ExecutionException, RetryException {
+ return retryer.call(callable);
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/RetryerBuilder.java b/antares-common/src/main/java/me/hao0/antares/common/retry/RetryerBuilder.java
new file mode 100644
index 00000000..91d98c0b
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/RetryerBuilder.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A builder used to configure and create a {@link Retryer}.
+ *
+ * @param result of a {@link Retryer}'s call, the type of the call return value
+ * @author JB
+ * @author Jason Dunkelberger (dirkraft)
+ */
+public class RetryerBuilder {
+ private AttemptTimeLimiter attemptTimeLimiter;
+ private StopStrategy stopStrategy;
+ private WaitStrategy waitStrategy;
+ private BlockStrategy blockStrategy;
+ private Predicate> rejectionPredicate = Predicates.alwaysFalse();
+ private List listeners = new ArrayList();
+
+ private RetryerBuilder() {
+ }
+
+ /**
+ * Constructs a new builder
+ *
+ * @param result of a {@link Retryer}'s call, the type of the call return value
+ * @return the new builder
+ */
+ public static RetryerBuilder newBuilder() {
+ return new RetryerBuilder();
+ }
+
+ /**
+ * Adds a listener that will be notified of each attempt that is made
+ *
+ * @param listener Listener to add
+ * @return this
+ */
+ public RetryerBuilder withRetryListener(@Nonnull RetryListener listener) {
+ Preconditions.checkNotNull(listener, "listener may not be null");
+ listeners.add(listener);
+ return this;
+ }
+
+ /**
+ * Sets the wait strategy used to decide how long to sleep between failed attempts.
+ * The default strategy is to retry immediately after a failed attempt.
+ *
+ * @param waitStrategy the strategy used to sleep between failed attempts
+ * @return this
+ * @throws IllegalStateException if a wait strategy has already been set.
+ */
+ public RetryerBuilder withWaitStrategy(@Nonnull WaitStrategy waitStrategy) throws IllegalStateException {
+ Preconditions.checkNotNull(waitStrategy, "waitStrategy may not be null");
+ Preconditions.checkState(this.waitStrategy == null, "a wait strategy has already been set %s", this.waitStrategy);
+ this.waitStrategy = waitStrategy;
+ return this;
+ }
+
+ /**
+ * Sets the stop strategy used to decide when to stop retrying. The default strategy is to not stop at all .
+ *
+ * @param stopStrategy the strategy used to decide when to stop retrying
+ * @return this
+ * @throws IllegalStateException if a stop strategy has already been set.
+ */
+ public RetryerBuilder withStopStrategy(@Nonnull StopStrategy stopStrategy) throws IllegalStateException {
+ Preconditions.checkNotNull(stopStrategy, "stopStrategy may not be null");
+ Preconditions.checkState(this.stopStrategy == null, "a stop strategy has already been set %s", this.stopStrategy);
+ this.stopStrategy = stopStrategy;
+ return this;
+ }
+
+
+ /**
+ * Sets the block strategy used to decide how to block between retry attempts. The default strategy is to use Thread#sleep().
+ *
+ * @param blockStrategy the strategy used to decide how to block between retry attempts
+ * @return this
+ * @throws IllegalStateException if a block strategy has already been set.
+ */
+ public RetryerBuilder withBlockStrategy(@Nonnull BlockStrategy blockStrategy) throws IllegalStateException {
+ Preconditions.checkNotNull(blockStrategy, "blockStrategy may not be null");
+ Preconditions.checkState(this.blockStrategy == null, "a block strategy has already been set %s", this.blockStrategy);
+ this.blockStrategy = blockStrategy;
+ return this;
+ }
+
+
+ /**
+ * Configures the retryer to limit the duration of any particular attempt by the given duration.
+ *
+ * @param attemptTimeLimiter to apply to each attempt
+ * @return this
+ */
+ public RetryerBuilder withAttemptTimeLimiter(@Nonnull AttemptTimeLimiter attemptTimeLimiter) {
+ Preconditions.checkNotNull(attemptTimeLimiter);
+ this.attemptTimeLimiter = attemptTimeLimiter;
+ return this;
+ }
+
+ /**
+ * Configures the retryer to retry if an exception (i.e. any Exception
or subclass
+ * of Exception
) is thrown by the call.
+ *
+ * @return this
+ */
+ public RetryerBuilder retryIfException() {
+ rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate(Exception.class));
+ return this;
+ }
+
+ /**
+ * Configures the retryer to retry if a runtime exception (i.e. any RuntimeException
or subclass
+ * of RuntimeException
) is thrown by the call.
+ *
+ * @return this
+ */
+ public RetryerBuilder retryIfRuntimeException() {
+ rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate(RuntimeException.class));
+ return this;
+ }
+
+ /**
+ * Configures the retryer to retry if an exception of the given class (or subclass of the given class) is
+ * thrown by the call.
+ *
+ * @param exceptionClass the type of the exception which should cause the retryer to retry
+ * @return this
+ */
+ public RetryerBuilder retryIfExceptionOfType(@Nonnull Class extends Throwable> exceptionClass) {
+ Preconditions.checkNotNull(exceptionClass, "exceptionClass may not be null");
+ rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate(exceptionClass));
+ return this;
+ }
+
+ /**
+ * Configures the retryer to retry if an exception satisfying the given predicate is
+ * thrown by the call.
+ *
+ * @param exceptionPredicate the predicate which causes a retry if satisfied
+ * @return this
+ */
+ public RetryerBuilder retryIfException(@Nonnull Predicate exceptionPredicate) {
+ Preconditions.checkNotNull(exceptionPredicate, "exceptionPredicate may not be null");
+ rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionPredicate(exceptionPredicate));
+ return this;
+ }
+
+ /**
+ * Configures the retryer to retry if the result satisfies the given predicate.
+ *
+ * @param resultPredicate a predicate applied to the result, and which causes the retryer
+ * to retry if the predicate is satisfied
+ * @return this
+ */
+ public RetryerBuilder retryIfResult(@Nonnull Predicate resultPredicate) {
+ Preconditions.checkNotNull(resultPredicate, "resultPredicate may not be null");
+ rejectionPredicate = Predicates.or(rejectionPredicate, new ResultPredicate(resultPredicate));
+ return this;
+ }
+
+ /**
+ * Builds the retryer.
+ *
+ * @return the built retryer.
+ */
+ public Retryer build() {
+ AttemptTimeLimiter theAttemptTimeLimiter = attemptTimeLimiter == null ? AttemptTimeLimiters.noTimeLimit() : attemptTimeLimiter;
+ StopStrategy theStopStrategy = stopStrategy == null ? StopStrategies.neverStop() : stopStrategy;
+ WaitStrategy theWaitStrategy = waitStrategy == null ? WaitStrategies.noWait() : waitStrategy;
+ BlockStrategy theBlockStrategy = blockStrategy == null ? BlockStrategies.threadSleepStrategy() : blockStrategy;
+
+ return new Retryer(theAttemptTimeLimiter, theStopStrategy, theWaitStrategy, theBlockStrategy, rejectionPredicate, listeners);
+ }
+
+ private static final class ExceptionClassPredicate implements Predicate> {
+
+ private Class extends Throwable> exceptionClass;
+
+ public ExceptionClassPredicate(Class extends Throwable> exceptionClass) {
+ this.exceptionClass = exceptionClass;
+ }
+
+ @Override
+ public boolean apply(Attempt attempt) {
+ if (!attempt.hasException()) {
+ return false;
+ }
+ return exceptionClass.isAssignableFrom(attempt.getExceptionCause().getClass());
+ }
+ }
+
+ private static final class ResultPredicate implements Predicate> {
+
+ private Predicate delegate;
+
+ public ResultPredicate(Predicate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean apply(Attempt attempt) {
+ if (!attempt.hasResult()) {
+ return false;
+ }
+ V result = attempt.getResult();
+ return delegate.apply(result);
+ }
+ }
+
+ private static final class ExceptionPredicate implements Predicate> {
+
+ private Predicate delegate;
+
+ public ExceptionPredicate(Predicate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean apply(Attempt attempt) {
+ if (!attempt.hasException()) {
+ return false;
+ }
+ return delegate.apply(attempt.getExceptionCause());
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/Retryers.java b/antares-common/src/main/java/me/hao0/antares/common/retry/Retryers.java
new file mode 100644
index 00000000..a8cb7d45
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/Retryers.java
@@ -0,0 +1,72 @@
+package me.hao0.antares.common.retry;
+
+import com.google.common.base.Predicate;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public final class Retryers {
+
+ private Retryers(){}
+
+ private static class RetryersHolder{
+ static Retryers INSTANCE = new Retryers();
+ }
+
+ public static Retryers get(){
+ return RetryersHolder.INSTANCE;
+ }
+
+ /**
+ * New a retryer
+ * @param p predicate that whether retry or not
+ * @param the generic type
+ * @return the retryer
+ */
+ public Retryer newRetryer(Predicate p){
+ return newRetryer(p, 3, -1, null);
+ }
+
+ /**
+ * New a retryer
+ * @param p predicate that whether retry or not
+ * @param the generic type
+ * @return the retryer
+ */
+ public Retryer newRetryer(Predicate p, int fixWaitSecs){
+ return newRetryer(p, fixWaitSecs, -1, null);
+ }
+
+ /**
+ * New a retryer
+ * @param p predicate that whether retry or not
+ * @param fixWaitSecs the fixed wait seconds per retry
+ * @param attemptTimes the times for retrying
+ * @param the generic type
+ * @return the retryer
+ */
+ public Retryer newRetryer(Predicate p, int fixWaitSecs, int attemptTimes, RetryListener retryListener){
+
+ RetryerBuilder builder = RetryerBuilder.newBuilder()
+ .retryIfResult(p)
+ .retryIfRuntimeException()
+ .withWaitStrategy(WaitStrategies.fixedWait(fixWaitSecs, TimeUnit.SECONDS));
+
+ // attempt times
+ if (attemptTimes > 0){
+ builder.withStopStrategy(StopStrategies.stopAfterAttempt(attemptTimes));
+ } else {
+ builder.withStopStrategy(StopStrategies.neverStop());
+ }
+
+ // listener
+ if (retryListener == null){
+ retryListener = new DefaultRetryListener();
+ }
+ builder.withRetryListener(retryListener);
+
+ return builder.build();
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/StopStrategies.java b/antares-common/src/main/java/me/hao0/antares/common/retry/StopStrategies.java
new file mode 100644
index 00000000..5067e2ac
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/StopStrategies.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import com.google.common.base.Preconditions;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Factory class for {@link StopStrategy} instances.
+ *
+ * @author JB
+ */
+public final class StopStrategies {
+ private static final StopStrategy NEVER_STOP = new NeverStopStrategy();
+
+ private StopStrategies() {
+ }
+
+ /**
+ * Returns a stop strategy which never stops retrying. It might be best to
+ * try not to abuse services with this kind of behavior when small wait
+ * intervals between retry attempts are being used.
+ *
+ * @return a stop strategy which never stops
+ */
+ public static StopStrategy neverStop() {
+ return NEVER_STOP;
+ }
+
+ /**
+ * Returns a stop strategy which stops after N failed attempts.
+ *
+ * @param attemptNumber the number of failed attempts before stopping
+ * @return a stop strategy which stops after {@code attemptNumber} attempts
+ */
+ public static StopStrategy stopAfterAttempt(int attemptNumber) {
+ return new StopAfterAttemptStrategy(attemptNumber);
+ }
+
+ /**
+ * Returns a stop strategy which stops after a given delay. If an
+ * unsuccessful attempt is made, this {@link StopStrategy} will check if the
+ * amount of time that's passed from the first attempt has exceeded the
+ * given delay amount. If it has exceeded this delay, then using this
+ * strategy causes the retrying to stop.
+ *
+ * @param delayInMillis the delay, in milliseconds, starting from first attempt
+ * @return a stop strategy which stops after {@code delayInMillis} time in milliseconds
+ * @deprecated Use {@link #stopAfterDelay(long, TimeUnit)} instead.
+ */
+ @Deprecated
+ public static StopStrategy stopAfterDelay(long delayInMillis) {
+ return stopAfterDelay(delayInMillis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Returns a stop strategy which stops after a given delay. If an
+ * unsuccessful attempt is made, this {@link StopStrategy} will check if the
+ * amount of time that's passed from the first attempt has exceeded the
+ * given delay amount. If it has exceeded this delay, then using this
+ * strategy causes the retrying to stop.
+ *
+ * @param duration the delay, starting from first attempt
+ * @param timeUnit the unit of the duration
+ * @return a stop strategy which stops after {@code delayInMillis} time in milliseconds
+ */
+ public static StopStrategy stopAfterDelay(long duration, @Nonnull TimeUnit timeUnit) {
+ Preconditions.checkNotNull(timeUnit, "The time unit may not be null");
+ return new StopAfterDelayStrategy(timeUnit.toMillis(duration));
+ }
+
+ @Immutable
+ private static final class NeverStopStrategy implements StopStrategy {
+ @Override
+ public boolean shouldStop(Attempt failedAttempt) {
+ return false;
+ }
+ }
+
+ @Immutable
+ private static final class StopAfterAttemptStrategy implements StopStrategy {
+ private final int maxAttemptNumber;
+
+ public StopAfterAttemptStrategy(int maxAttemptNumber) {
+ Preconditions.checkArgument(maxAttemptNumber >= 1, "maxAttemptNumber must be >= 1 but is %d", maxAttemptNumber);
+ this.maxAttemptNumber = maxAttemptNumber;
+ }
+
+ @Override
+ public boolean shouldStop(Attempt failedAttempt) {
+ return failedAttempt.getAttemptNumber() >= maxAttemptNumber;
+ }
+ }
+
+ @Immutable
+ private static final class StopAfterDelayStrategy implements StopStrategy {
+ private final long maxDelay;
+
+ public StopAfterDelayStrategy(long maxDelay) {
+ Preconditions.checkArgument(maxDelay >= 0L, "maxDelay must be >= 0 but is %d", maxDelay);
+ this.maxDelay = maxDelay;
+ }
+
+ @Override
+ public boolean shouldStop(Attempt failedAttempt) {
+ return failedAttempt.getDelaySinceFirstAttempt() >= maxDelay;
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/StopStrategy.java b/antares-common/src/main/java/me/hao0/antares/common/retry/StopStrategy.java
new file mode 100644
index 00000000..fe33b843
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/StopStrategy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+/**
+ * A strategy used to decide if a retryer must stop retrying after a failed attempt or not.
+ *
+ * @author JB
+ */
+public interface StopStrategy {
+
+ /**
+ * Returns true
if the retryer should stop retrying.
+ *
+ * @param failedAttempt the previous failed {@code Attempt}
+ * @return true
if the retryer must stop, false
otherwise
+ */
+ boolean shouldStop(Attempt failedAttempt);
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/WaitStrategies.java b/antares-common/src/main/java/me/hao0/antares/common/retry/WaitStrategies.java
new file mode 100644
index 00000000..095fd160
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/WaitStrategies.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Factory class for instances of {@link WaitStrategy}.
+ *
+ * @author JB
+ */
+public final class WaitStrategies {
+
+ private static final WaitStrategy NO_WAIT_STRATEGY = new FixedWaitStrategy(0L);
+
+ private WaitStrategies() {
+ }
+
+ /**
+ * Returns a wait strategy that doesn't sleep at all before retrying. Use this at your own risk.
+ *
+ * @return a wait strategy that doesn't wait between retries
+ */
+ public static WaitStrategy noWait() {
+ return NO_WAIT_STRATEGY;
+ }
+
+ /**
+ * Returns a wait strategy that sleeps a fixed amount of time before retrying.
+ *
+ * @param sleepTime the time to sleep
+ * @param timeUnit the unit of the time to sleep
+ * @return a wait strategy that sleeps a fixed amount of time
+ * @throws IllegalStateException if the sleep time is < 0
+ */
+ public static WaitStrategy fixedWait(long sleepTime, @Nonnull TimeUnit timeUnit) throws IllegalStateException {
+ Preconditions.checkNotNull(timeUnit, "The time unit may not be null");
+ return new FixedWaitStrategy(timeUnit.toMillis(sleepTime));
+ }
+
+ /**
+ * Returns a strategy that sleeps a random amount of time before retrying.
+ *
+ * @param maximumTime the maximum time to sleep
+ * @param timeUnit the unit of the maximum time
+ * @return a wait strategy with a random wait time
+ * @throws IllegalStateException if the maximum sleep time is <= 0.
+ */
+ public static WaitStrategy randomWait(long maximumTime, @Nonnull TimeUnit timeUnit) {
+ Preconditions.checkNotNull(timeUnit, "The time unit may not be null");
+ return new RandomWaitStrategy(0L, timeUnit.toMillis(maximumTime));
+ }
+
+ /**
+ * Returns a strategy that sleeps a random amount of time before retrying.
+ *
+ * @param minimumTime the minimum time to sleep
+ * @param minimumTimeUnit the unit of the minimum time
+ * @param maximumTime the maximum time to sleep
+ * @param maximumTimeUnit the unit of the maximum time
+ * @return a wait strategy with a random wait time
+ * @throws IllegalStateException if the minimum sleep time is < 0, or if the
+ * maximum sleep time is less than (or equals to) the minimum.
+ */
+ public static WaitStrategy randomWait(long minimumTime,
+ @Nonnull TimeUnit minimumTimeUnit,
+ long maximumTime,
+ @Nonnull TimeUnit maximumTimeUnit) {
+ Preconditions.checkNotNull(minimumTimeUnit, "The minimum time unit may not be null");
+ Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
+ return new RandomWaitStrategy(minimumTimeUnit.toMillis(minimumTime),
+ maximumTimeUnit.toMillis(maximumTime));
+ }
+
+ /**
+ * Returns a strategy that sleeps a fixed amount of time after the first
+ * failed attempt and in incrementing amounts of time after each additional
+ * failed attempt.
+ *
+ * @param initialSleepTime the time to sleep before retrying the first time
+ * @param initialSleepTimeUnit the unit of the initial sleep time
+ * @param increment the increment added to the previous sleep time after each failed attempt
+ * @param incrementTimeUnit the unit of the increment
+ * @return a wait strategy that incrementally sleeps an additional fixed time after each failed attempt
+ */
+ public static WaitStrategy incrementingWait(long initialSleepTime,
+ @Nonnull TimeUnit initialSleepTimeUnit,
+ long increment,
+ @Nonnull TimeUnit incrementTimeUnit) {
+ Preconditions.checkNotNull(initialSleepTimeUnit, "The initial sleep time unit may not be null");
+ Preconditions.checkNotNull(incrementTimeUnit, "The increment time unit may not be null");
+ return new IncrementingWaitStrategy(initialSleepTimeUnit.toMillis(initialSleepTime),
+ incrementTimeUnit.toMillis(increment));
+ }
+
+ /**
+ * Returns a strategy which sleeps for an exponential amount of time after the first failed attempt,
+ * and in exponentially incrementing amounts after each failed attempt up to Long.MAX_VALUE.
+ *
+ * @return a wait strategy that increments with each failed attempt using exponential backoff
+ */
+ public static WaitStrategy exponentialWait() {
+ return new ExponentialWaitStrategy(1, Long.MAX_VALUE);
+ }
+
+ /**
+ * Returns a strategy which sleeps for an exponential amount of time after the first failed attempt,
+ * and in exponentially incrementing amounts after each failed attempt up to the maximumTime.
+ *
+ * @param maximumTime the maximum time to sleep
+ * @param maximumTimeUnit the unit of the maximum time
+ * @return a wait strategy that increments with each failed attempt using exponential backoff
+ */
+ public static WaitStrategy exponentialWait(long maximumTime,
+ @Nonnull TimeUnit maximumTimeUnit) {
+ Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
+ return new ExponentialWaitStrategy(1, maximumTimeUnit.toMillis(maximumTime));
+ }
+
+ /**
+ * Returns a strategy which sleeps for an exponential amount of time after the first failed attempt,
+ * and in exponentially incrementing amounts after each failed attempt up to the maximumTime.
+ * The wait time between the retries can be controlled by the multiplier.
+ * nextWaitTime = exponentialIncrement * {@code multiplier}.
+ *
+ * @param multiplier multiply the wait time calculated by this
+ * @param maximumTime the maximum time to sleep
+ * @param maximumTimeUnit the unit of the maximum time
+ * @return a wait strategy that increments with each failed attempt using exponential backoff
+ */
+ public static WaitStrategy exponentialWait(long multiplier,
+ long maximumTime,
+ @Nonnull TimeUnit maximumTimeUnit) {
+ Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
+ return new ExponentialWaitStrategy(multiplier, maximumTimeUnit.toMillis(maximumTime));
+ }
+
+ /**
+ * Returns a strategy which sleeps for an increasing amount of time after the first failed attempt,
+ * and in Fibonacci increments after each failed attempt up to {@link Long#MAX_VALUE}.
+ *
+ * @return a wait strategy that increments with each failed attempt using a Fibonacci sequence
+ */
+ public static WaitStrategy fibonacciWait() {
+ return new FibonacciWaitStrategy(1, Long.MAX_VALUE);
+ }
+
+ /**
+ * Returns a strategy which sleeps for an increasing amount of time after the first failed attempt,
+ * and in Fibonacci increments after each failed attempt up to the {@code maximumTime}.
+ *
+ * @param maximumTime the maximum time to sleep
+ * @param maximumTimeUnit the unit of the maximum time
+ * @return a wait strategy that increments with each failed attempt using a Fibonacci sequence
+ */
+ public static WaitStrategy fibonacciWait(long maximumTime,
+ @Nonnull TimeUnit maximumTimeUnit) {
+ Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
+ return new FibonacciWaitStrategy(1, maximumTimeUnit.toMillis(maximumTime));
+ }
+
+ /**
+ * Returns a strategy which sleeps for an increasing amount of time after the first failed attempt,
+ * and in Fibonacci increments after each failed attempt up to the {@code maximumTime}.
+ * The wait time between the retries can be controlled by the multiplier.
+ * nextWaitTime = fibonacciIncrement * {@code multiplier}.
+ *
+ * @param multiplier multiply the wait time calculated by this
+ * @param maximumTime the maximum time to sleep
+ * @param maximumTimeUnit the unit of the maximum time
+ * @return a wait strategy that increments with each failed attempt using a Fibonacci sequence
+ */
+ public static WaitStrategy fibonacciWait(long multiplier,
+ long maximumTime,
+ @Nonnull TimeUnit maximumTimeUnit) {
+ Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
+ return new FibonacciWaitStrategy(multiplier, maximumTimeUnit.toMillis(maximumTime));
+ }
+
+ /**
+ * Returns a strategy which sleeps for an amount of time based on the Exception that occurred. The
+ * {@code function} determines how the sleep time should be calculated for the given
+ * {@code exceptionClass}. If the exception does not match, a wait time of 0 is returned.
+ *
+ * @param function function to calculate sleep time
+ * @param exceptionClass class to calculate sleep time from
+ * @return a wait strategy calculated from the failed attempt
+ */
+ public static WaitStrategy exceptionWait(@Nonnull Class exceptionClass,
+ @Nonnull Function function) {
+ Preconditions.checkNotNull(exceptionClass, "exceptionClass may not be null");
+ Preconditions.checkNotNull(function, "function may not be null");
+ return new ExceptionWaitStrategy(exceptionClass, function);
+ }
+
+ /**
+ * Joins one or more wait strategies to derive a composite wait strategy.
+ * The new joined strategy will have a wait time which is total of all wait times computed one after another in order.
+ *
+ * @param waitStrategies Wait strategies that need to be applied one after another for computing the sleep time.
+ * @return A composite wait strategy
+ */
+ public static WaitStrategy join(WaitStrategy... waitStrategies) {
+ Preconditions.checkState(waitStrategies.length > 0, "Must have at least one wait strategy");
+ List waitStrategyList = Lists.newArrayList(waitStrategies);
+ Preconditions.checkState(!waitStrategyList.contains(null), "Cannot have a null wait strategy");
+ return new CompositeWaitStrategy(waitStrategyList);
+ }
+
+ @Immutable
+ private static final class FixedWaitStrategy implements WaitStrategy {
+ private final long sleepTime;
+
+ public FixedWaitStrategy(long sleepTime) {
+ Preconditions.checkArgument(sleepTime >= 0L, "sleepTime must be >= 0 but is %d", sleepTime);
+ this.sleepTime = sleepTime;
+ }
+
+ @Override
+ public long computeSleepTime(Attempt failedAttempt) {
+ return sleepTime;
+ }
+ }
+
+ @Immutable
+ private static final class RandomWaitStrategy implements WaitStrategy {
+ private static final Random RANDOM = new Random();
+ private final long minimum;
+ private final long maximum;
+
+ public RandomWaitStrategy(long minimum, long maximum) {
+ Preconditions.checkArgument(minimum >= 0, "minimum must be >= 0 but is %d", minimum);
+ Preconditions.checkArgument(maximum > minimum, "maximum must be > minimum but maximum is %d and minimum is", maximum, minimum);
+
+ this.minimum = minimum;
+ this.maximum = maximum;
+ }
+
+ @Override
+ public long computeSleepTime(Attempt failedAttempt) {
+ long t = Math.abs(RANDOM.nextLong()) % (maximum - minimum);
+ return t + minimum;
+ }
+ }
+
+ @Immutable
+ private static final class IncrementingWaitStrategy implements WaitStrategy {
+ private final long initialSleepTime;
+ private final long increment;
+
+ public IncrementingWaitStrategy(long initialSleepTime,
+ long increment) {
+ Preconditions.checkArgument(initialSleepTime >= 0L, "initialSleepTime must be >= 0 but is %d", initialSleepTime);
+ this.initialSleepTime = initialSleepTime;
+ this.increment = increment;
+ }
+
+ @Override
+ public long computeSleepTime(Attempt failedAttempt) {
+ long result = initialSleepTime + (increment * (failedAttempt.getAttemptNumber() - 1));
+ return result >= 0L ? result : 0L;
+ }
+ }
+
+ @Immutable
+ private static final class ExponentialWaitStrategy implements WaitStrategy {
+ private final long multiplier;
+ private final long maximumWait;
+
+ public ExponentialWaitStrategy(long multiplier,
+ long maximumWait) {
+ Preconditions.checkArgument(multiplier > 0L, "multiplier must be > 0 but is %d", multiplier);
+ Preconditions.checkArgument(maximumWait >= 0L, "maximumWait must be >= 0 but is %d", maximumWait);
+ Preconditions.checkArgument(multiplier < maximumWait, "multiplier must be < maximumWait but is %d", multiplier);
+ this.multiplier = multiplier;
+ this.maximumWait = maximumWait;
+ }
+
+ @Override
+ public long computeSleepTime(Attempt failedAttempt) {
+ double exp = Math.pow(2, failedAttempt.getAttemptNumber());
+ long result = Math.round(multiplier * exp);
+ if (result > maximumWait) {
+ result = maximumWait;
+ }
+ return result >= 0L ? result : 0L;
+ }
+ }
+
+ @Immutable
+ private static final class FibonacciWaitStrategy implements WaitStrategy {
+ private final long multiplier;
+ private final long maximumWait;
+
+ public FibonacciWaitStrategy(long multiplier, long maximumWait) {
+ Preconditions.checkArgument(multiplier > 0L, "multiplier must be > 0 but is %d", multiplier);
+ Preconditions.checkArgument(maximumWait >= 0L, "maximumWait must be >= 0 but is %d", maximumWait);
+ Preconditions.checkArgument(multiplier < maximumWait, "multiplier must be < maximumWait but is %d", multiplier);
+ this.multiplier = multiplier;
+ this.maximumWait = maximumWait;
+ }
+
+ @Override
+ public long computeSleepTime(Attempt failedAttempt) {
+ long fib = fib(failedAttempt.getAttemptNumber());
+ long result = multiplier * fib;
+
+ if (result > maximumWait || result < 0L) {
+ result = maximumWait;
+ }
+
+ return result >= 0L ? result : 0L;
+ }
+
+ private long fib(long n) {
+ if (n == 0L) return 0L;
+ if (n == 1L) return 1L;
+
+ long prevPrev = 0L;
+ long prev = 1L;
+ long result = 0L;
+
+ for (long i = 2L; i <= n; i++) {
+ result = prev + prevPrev;
+ prevPrev = prev;
+ prev = result;
+ }
+
+ return result;
+ }
+ }
+
+ @Immutable
+ private static final class CompositeWaitStrategy implements WaitStrategy {
+ private final List waitStrategies;
+
+ public CompositeWaitStrategy(List waitStrategies) {
+ Preconditions.checkState(!waitStrategies.isEmpty(), "Need at least one wait strategy");
+ this.waitStrategies = waitStrategies;
+ }
+
+ @Override
+ public long computeSleepTime(Attempt failedAttempt) {
+ long waitTime = 0L;
+ for (WaitStrategy waitStrategy : waitStrategies) {
+ waitTime += waitStrategy.computeSleepTime(failedAttempt);
+ }
+ return waitTime;
+ }
+ }
+
+ @Immutable
+ private static final class ExceptionWaitStrategy implements WaitStrategy {
+ private final Class exceptionClass;
+ private final Function function;
+
+ public ExceptionWaitStrategy(@Nonnull Class exceptionClass, @Nonnull Function function) {
+ this.exceptionClass = exceptionClass;
+ this.function = function;
+ }
+
+ @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "ConstantConditions", "unchecked"})
+ @Override
+ public long computeSleepTime(Attempt lastAttempt) {
+ if (lastAttempt.hasException()) {
+ Throwable cause = lastAttempt.getExceptionCause();
+ if (exceptionClass.isAssignableFrom(cause.getClass())) {
+ return function.apply((T) cause);
+ }
+ }
+ return 0L;
+ }
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/retry/WaitStrategy.java b/antares-common/src/main/java/me/hao0/antares/common/retry/WaitStrategy.java
new file mode 100644
index 00000000..1a6bfdc6
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/retry/WaitStrategy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-2015 Ray Holder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.hao0.antares.common.retry;
+
+/**
+ * A strategy used to decide how long to sleep before retrying after a failed attempt.
+ *
+ * @author JB
+ */
+public interface WaitStrategy {
+
+ /**
+ * Returns the time, in milliseconds, to sleep before retrying.
+ *
+ * @param failedAttempt the previous failed {@code Attempt}
+ * @return the sleep time before next attempt
+ */
+ long computeSleepTime(Attempt failedAttempt);
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/support/Component.java b/antares-common/src/main/java/me/hao0/antares/common/support/Component.java
new file mode 100644
index 00000000..682eda84
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/support/Component.java
@@ -0,0 +1,53 @@
+package me.hao0.antares.common.support;
+
+/**
+ * A lifecycle component can reuse
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public abstract class Component implements Lifecycle {
+
+ protected volatile boolean started;
+
+ protected volatile boolean shutdowned;
+
+ @Override
+ public boolean isStart() {
+ return started;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return shutdowned;
+ }
+
+ @Override
+ public void start() {
+
+ if(isStart()){
+ return;
+ }
+
+ doStart();
+
+ started = true;
+ shutdowned = false;
+ }
+
+ protected abstract void doStart();
+
+ @Override
+ public void shutdown() {
+
+ if (isShutdown()){
+ return;
+ }
+
+ doShutdown();
+
+ shutdowned = true;
+ started = false;
+ }
+
+ protected abstract void doShutdown();
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/support/Lifecycle.java b/antares-common/src/main/java/me/hao0/antares/common/support/Lifecycle.java
new file mode 100644
index 00000000..05d14f2a
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/support/Lifecycle.java
@@ -0,0 +1,16 @@
+package me.hao0.antares.common.support;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public interface Lifecycle {
+
+ void start();
+
+ boolean isStart();
+
+ void shutdown();
+
+ boolean isShutdown();
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/support/SimpleJobStateMachine.java b/antares-common/src/main/java/me/hao0/antares/common/support/SimpleJobStateMachine.java
new file mode 100644
index 00000000..94ebab62
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/support/SimpleJobStateMachine.java
@@ -0,0 +1,86 @@
+package me.hao0.antares.common.support;
+
+import me.hao0.antares.common.model.enums.JobState;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A simple job state machine
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public class SimpleJobStateMachine {
+
+ /**
+ * state set
+ *
+ * A state : the allowable previous state set
+ *
+ */
+ private final Map> states = new HashMap<>();
+
+ private SimpleJobStateMachine(){
+
+ configure(JobState.WAITING, JobState.RUNNING);
+
+ configure(JobState.PAUSED, JobState.WAITING);
+ configure(JobState.RUNNING, JobState.WAITING);
+
+ configure(JobState.WAITING, JobState.PAUSED);
+ configure(JobState.RUNNING, JobState.PAUSED);
+ configure(JobState.FAILED, JobState.PAUSED);
+
+ configure(JobState.PAUSED, JobState.STOPPED);
+ configure(JobState.WAITING, JobState.STOPPED);
+ configure(JobState.RUNNING, JobState.STOPPED);
+ configure(JobState.FAILED, JobState.STOPPED);
+
+ // may be failed when update the job to waiting after finish job
+ configure(JobState.WAITING, JobState.FAILED);
+ configure(JobState.RUNNING, JobState.FAILED);
+
+ }
+
+ /**
+ * Configure a state transfer
+ * @param prev the previous state
+ * @param next the next state
+ *
+ * prev -> next
+ *
+ */
+ private void configure(JobState prev, JobState next){
+ Set previousStates = states.get(next);
+ if (previousStates == null){
+ previousStates = new HashSet<>();
+ states.put(next, previousStates);
+ }
+ previousStates.add(prev);
+ }
+
+ /**
+ * Allow to transfer
+ * @param current the current state
+ * @param target the target state
+ * @return return true if allow, or false
+ */
+ public Boolean allow(JobState current, JobState target){
+ Set allows = states.get(target);
+ if (allows == null || allows.isEmpty()){
+ return Boolean.FALSE;
+ }
+ return allows.contains(current);
+ }
+
+ private static class SimpleJobStateMachineHolder{
+ static SimpleJobStateMachine INSTANCE = new SimpleJobStateMachine();
+ }
+
+ public static SimpleJobStateMachine get(){
+ return SimpleJobStateMachineHolder.INSTANCE;
+ }
+
+
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/util/ClientUris.java b/antares-common/src/main/java/me/hao0/antares/common/util/ClientUris.java
new file mode 100644
index 00000000..d5e852cc
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/util/ClientUris.java
@@ -0,0 +1,16 @@
+package me.hao0.antares.common.util;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public interface ClientUris {
+
+ String CLIENTS = "/clients";
+
+ String SHARD_PULL = "/shard_pull";
+
+ String SHARD_RETURN = "/shard_return";
+
+ String SHARD_FINISH = "/shard_finish";
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/util/CollectionUtil.java b/antares-common/src/main/java/me/hao0/antares/common/util/CollectionUtil.java
new file mode 100644
index 00000000..2e01518d
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/util/CollectionUtil.java
@@ -0,0 +1,19 @@
+package me.hao0.antares.common.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public final class CollectionUtil {
+
+ public static boolean isNullOrEmpty(Collection c){
+ return c == null || c.isEmpty();
+ }
+
+ public static boolean isNullOrEmpty(Map, ?> m){
+ return m == null || m.isEmpty();
+ }
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/util/Constants.java b/antares-common/src/main/java/me/hao0/antares/common/util/Constants.java
new file mode 100644
index 00000000..2a48a3f8
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/util/Constants.java
@@ -0,0 +1,30 @@
+package me.hao0.antares.common.util;
+
+/**
+ * Author: haolin
+ * Email: haolin.h0@gmail.com
+ */
+public interface Constants {
+
+ String APP_NAME_HEADER = "appName";
+
+ String APP_KEY_HEADER = "appSecret";
+
+ String CLIENT_VERSION_HEADER = "Client-Ver";
+
+ String CLIENT_LANG_HEADER = "Client-Lang";
+
+ String HTTP_PREFIX = "http://";
+
+ String JOB_SHARD_PARAMS_DELIMITER = ";";
+
+ String JOB_SHARD_PARAMS_KV_DELIMITER = "=";
+
+ Integer DEFAULT_LIST_BATCH_SIZE = 100;
+
+ String SCRIPT_JOB_ENV_SHARD_ITEM = "JOB_SHARD_ITEM";
+
+ String SCRIPT_JOB_ENV_SHARD_PARAM = "JOB_SHARD_PARAM";
+
+ Integer MAX_ERROR_LENGTH = 2048;
+}
diff --git a/antares-common/src/main/java/me/hao0/antares/common/util/Crons.java b/antares-common/src/main/java/me/hao0/antares/common/util/Crons.java
new file mode 100644
index 00000000..bf60ea03
--- /dev/null
+++ b/antares-common/src/main/java/me/hao0/antares/common/util/Crons.java
@@ -0,0 +1,1472 @@
+package me.hao0.antares.common.util;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+public final class Crons implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet