package me.hao0.antares.client.job.execute;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import me.hao0.antares.client.core.AntaresClient;
import me.hao0.antares.client.job.DefaultJob;
import me.hao0.antares.client.job.Job;
import me.hao0.antares.client.job.JobContext;
import me.hao0.antares.client.job.JobResult;
import me.hao0.antares.client.job.JobContextImpl;
import me.hao0.antares.client.job.listener.JobListener;
import me.hao0.antares.client.job.listener.JobResultListener;
import me.hao0.antares.client.job.script.DefaultScriptExecutor;
import me.hao0.antares.client.job.script.ScriptJob;
import me.hao0.antares.client.job.script.ScriptExecutor;
import me.hao0.antares.common.dto.PullShard;
import me.hao0.antares.common.dto.ShardFinishDto;
import me.hao0.antares.common.model.enums.ShardOperateRespCode;
import me.hao0.antares.common.support.Component;
import static me.hao0.antares.common.util.Constants.*;
import me.hao0.antares.common.util.Executors;
import me.hao0.antares.common.util.Systems;
import me.hao0.antares.common.util.ZkPaths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ExecutorService;
/**
* The abstract job executor
* Author: haolin
* Email: haolin.h0@gmail.com
*/
public abstract class AbstractJobExecutor extends Component implements JobExecutor {
private final Logger log = LoggerFactory.getLogger(AbstractJobExecutor.class);
protected final AntaresClient client;
private ExecutorService executor;
/**
* The script executor
*/
private final ScriptExecutor scriptExecutor = new DefaultScriptExecutor();
public AbstractJobExecutor(AntaresClient client) {
this.client = client;
}
@Override
public void doStart() {
executor = Executors.newExecutor(client.getExecutorThreadCount(), 10000, "JOB-EXECUTOR-");
}
@Override
public void doShutdown() {
executor.shutdown();
}
public void execute(final Long instanceId, final ZkJob zkJob) {
executor.submit(new ExecuteShardTask(instanceId, zkJob));
}
/**
* The task for executing the job shard
*/
private class ExecuteShardTask implements Runnable{
private final Long instanceId;
private final ZkJob zkJob;
public ExecuteShardTask(Long instanceId, ZkJob zkJob) {
this.instanceId = instanceId;
this.zkJob = zkJob;
}
@Override
public void run() {
try {
PullShard oneShard;
for(;;){
// pull until has one shard, or the job instance is finished
oneShard = pullShard(instanceId, zkJob);
if (oneShard == null){
// no need to continue
break;
}
doExecuteShard(instanceId, zkJob, oneShard);
// continue to pull
}
} catch (Exception e){
log.error("failed to execute shard task(job={}, instanceId={}), cause: {}",
zkJob.getJob(), instanceId, Throwables.getStackTraceAsString(e));
}
}
}
private void doExecuteShard(Long instanceId, ZkJob zkJob, PullShard oneShard) {
// build the job context
JobContext context = buildJobContext(instanceId, oneShard);
// invoke the job
Job job = zkJob.getJob();
// listener on before execute
if (job instanceof JobListener){
((JobListener) job).onBefore(context);
}
Date startTime = new Date();
JobResult res = null;
if (job instanceof DefaultJob){
// execute common job
res = job.execute(context);
} else if (job instanceof ScriptJob){
// execute script job
res = executeScript(context);
}
Date endTime = new Date();
// listener on after execute
if (job instanceof JobListener){
((JobListener) job).onAfter(context, res);
}
// handle the job result
if (res == null || res.is(JobResult.SUCCESS)){
// success
// write the instance shard to finish
ShardFinishDto shardFinishDto = buildShardFinishDto(instanceId, context.getShardId(), startTime, endTime);
shardFinishDto.setSuccess(Boolean.TRUE);
finishShard(shardFinishDto, zkJob);
// callback on success
if (job instanceof JobResultListener){
((JobResultListener)job).onSuccess();
}
} else if(res.is(JobResult.FAIL)){
// fail
ShardFinishDto shardFinishDto = buildShardFinishDto(instanceId, context.getShardId(), startTime, endTime);
shardFinishDto.setSuccess(Boolean.FALSE);
shardFinishDto.setCause(res.getError());
finishShard(shardFinishDto, zkJob);
// callback on fail
if (job instanceof JobResultListener){
((JobResultListener)job).onFail();
}
} else if(res.is(JobResult.LATER)){
// later
// return the shard to server, and pulled by other clients
returnShard(instanceId, context.getShardId(), zkJob);
}
}
private JobResult executeScript(JobContext context) {
String cmd = context.getJobParam();
Map<String, String> env = Maps.newHashMapWithExpectedSize(2);
env.put(SCRIPT_JOB_ENV_SHARD_ITEM, context.getShardId() + "");
if (Strings.isNullOrEmpty(context.getShardParam())){
env.put(SCRIPT_JOB_ENV_SHARD_PARAM, context.getShardParam());
}
return scriptExecutor.exec(cmd, env);
}
private JobContext buildJobContext(Long instanceId, PullShard shard) {
JobContext context = new JobContextImpl();
context.setInstanceId(instanceId);
context.setShardId(shard.getId());
context.setShardParam(shard.getParam());
context.setShardItem(shard.getItem());
context.setJobParam(shard.getJobParam());
context.setTotalShardCount(shard.getTotalShardCount());
return context;
}
private ShardFinishDto buildShardFinishDto(Long instanceId, Long shardId, Date startTime, Date endTime) {
ShardFinishDto shardFinishDto = new ShardFinishDto();
shardFinishDto.setInstanceId(instanceId);
shardFinishDto.setShardId(shardId);
shardFinishDto.setClient(Systems.hostPid());
shardFinishDto.setStartTime(startTime);
shardFinishDto.setEndTime(endTime);
return shardFinishDto;
}
protected void checkInvalidInstance(Long instanceId, ZkJob zkJob, ShardOperateRespCode code) {
if (code != null){
if (ShardOperateRespCode.needCleanJobInstance(code)){
// clean the dirty zk job instance
String jobInstancePath = ZkPaths.pathOfJobInstance(client.getAppName(), zkJob.getJobClass(), instanceId);
client.getZk().deleteIfExists(jobInstancePath);
}
}
}
/**
* Pull an available shard to convert to the job context
* @param instanceId the job instance id
* @param zkJob the zk job
* @return the job context, the job won't execute if return null
*/
protected abstract PullShard pullShard(Long instanceId, ZkJob zkJob);
/**
* Push the shard for pulling later
* @param instanceId the job instance id
* @param shardId the shard id
* @param zkJob the zk job
* @return return true if push successfully, or false
*/
protected abstract Boolean returnShard(Long instanceId, Long shardId, ZkJob zkJob);
/**
* Finish the shard
* @param shardFinishDto the finish shard dto
* @param zkJob the zk job
* @return return true if finish successfully, or false
*/
protected abstract Boolean finishShard(ShardFinishDto shardFinishDto, ZkJob zkJob);
}