package io.cattle.platform.engine.manager.impl.jooq;
import static io.cattle.platform.core.model.tables.ProcessExecutionTable.*;
import static io.cattle.platform.core.model.tables.ProcessInstanceTable.*;
import io.cattle.platform.archaius.util.ArchaiusUtil;
import io.cattle.platform.core.model.ProcessInstance;
import io.cattle.platform.core.model.tables.records.ProcessInstanceRecord;
import io.cattle.platform.db.jooq.dao.impl.AbstractJooqDao;
import io.cattle.platform.engine.manager.impl.ProcessRecord;
import io.cattle.platform.engine.manager.impl.ProcessRecordDao;
import io.cattle.platform.engine.process.ExitReason;
import io.cattle.platform.engine.process.ProcessPhase;
import io.cattle.platform.engine.process.ProcessResult;
import io.cattle.platform.engine.process.log.ProcessLog;
import io.cattle.platform.engine.server.ProcessInstanceReference;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.object.ObjectManager;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.jooq.Condition;
import org.jooq.Record6;
import org.jooq.RecordHandler;
import org.jooq.impl.DSL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.config.DynamicIntProperty;
public class JooqProcessRecordDao extends AbstractJooqDao implements ProcessRecordDao {
private static final Logger log = LoggerFactory.getLogger(JooqProcessRecordDao.class);
private static final DynamicIntProperty BATCH = ArchaiusUtil.getInt("process.replay.batch.size");
@Inject
JsonMapper jsonMapper;
@Inject
ObjectManager objectManager;
@Override
public List<ProcessInstanceReference> pendingTasks() {
return pendingTasks(null, null);
}
@Override
public Long nextTask(String resourceType, String resourceId) {
List<ProcessInstanceReference> refs = pendingTasks(resourceType, resourceId);
return refs.size() == 0 ? null : refs.get(0).getProcessId();
}
protected List<ProcessInstanceReference> pendingTasks(String resourceType, String resourceId) {
final List<ProcessInstanceReference> result = new ArrayList<ProcessInstanceReference>();
final Set<String> seen = new HashSet<String>();
create()
.select(PROCESS_INSTANCE.ID,
PROCESS_INSTANCE.PROCESS_NAME,
PROCESS_INSTANCE.RESOURCE_TYPE,
PROCESS_INSTANCE.RESOURCE_ID,
PROCESS_INSTANCE.ACCOUNT_ID,
PROCESS_INSTANCE.PRIORITY)
.from(PROCESS_INSTANCE)
.where(processCondition(resourceType, resourceId))
.and(runAfterCondition(resourceType))
.limit(resourceType == null ? BATCH.get() : 1)
.fetchInto(new RecordHandler<Record6<Long, String, String, String, Long, Integer>>() {
@Override
public void next(Record6<Long, String, String, String, Long, Integer> record) {
String resource = String.format("%s:%s", record.getValue(PROCESS_INSTANCE.RESOURCE_TYPE),
record.getValue(PROCESS_INSTANCE.RESOURCE_ID));
if (seen.contains(resource)) {
return;
}
ProcessInstanceReference ref = new ProcessInstanceReference();
ref.setProcessId(record.getValue(PROCESS_INSTANCE.ID));
ref.setName(record.getValue(PROCESS_INSTANCE.PROCESS_NAME));
Integer priority = record.getValue(PROCESS_INSTANCE.PRIORITY);
if (priority != null) {
ref.setPriority(priority);
}
seen.add(resource);
result.add(ref);
}
});
return result;
}
protected Condition runAfterCondition(String resourceType) {
if (resourceType == null) {
return PROCESS_INSTANCE.RUN_AFTER.isNull()
.or(PROCESS_INSTANCE.RUN_AFTER.le(new Date()));
}
return DSL.trueCondition();
}
protected Condition processCondition(String resourceType, String resourceId) {
if (resourceType == null) {
return PROCESS_INSTANCE.END_TIME.isNull();
} else {
return PROCESS_INSTANCE.END_TIME.isNull().and(PROCESS_INSTANCE.RESOURCE_TYPE.eq(resourceType)).and(PROCESS_INSTANCE.RESOURCE_ID.eq(resourceId));
}
}
@Override
public ProcessRecord getRecord(Long id) {
io.cattle.platform.core.model.ProcessInstance record = create().selectFrom(PROCESS_INSTANCE).where(PROCESS_INSTANCE.ID.eq(id)).fetchOne();
if (record == null) {
return null;
}
ProcessRecord result = new ProcessRecord();
result.setId(record.getId());
result.setStartTime(toTimestamp(record.getStartTime()));
result.setEndTime(toTimestamp(record.getEndTime()));
result.setProcessLog(new ProcessLog());
result.setResult(EnumUtils.getEnum(ProcessResult.class, record.getResult()));
result.setExitReason(EnumUtils.getEnum(ExitReason.class, record.getExitReason()));
result.setPhase(EnumUtils.getEnum(ProcessPhase.class, record.getPhase()));
result.setStartProcessServerId(record.getStartProcessServerId());
result.setRunningProcessServerId(record.getRunningProcessServerId());
result.setExecutionCount(record.getExecutionCount());
result.setRunAfter(record.getRunAfter());
result.setAccountId(record.getAccountId());
result.setPriority(record.getPriority());
result.setResourceType(record.getResourceType());
result.setResourceId(record.getResourceId());
result.setProcessName(record.getProcessName());
result.setData(new HashMap<String, Object>(record.getData()));
return result;
}
@Override
public ProcessRecord insert(ProcessRecord record) {
ProcessInstanceRecord pi = create().newRecord(PROCESS_INSTANCE);
merge(pi, record);
pi.insert();
ProcessRecord newRecord = getRecord(pi.getId());
newRecord.setPredicate(record.getPredicate());
newRecord.setParentProcessState(record.getParentProcessState());
return newRecord;
}
@Override
public void update(ProcessRecord record, boolean schedule) {
ProcessInstanceRecord pi = create()
.selectFrom(PROCESS_INSTANCE)
.where(PROCESS_INSTANCE.ID.eq(record.getId()))
.fetchOne();
if (pi == null) {
throw new IllegalStateException("Failed to find process instance for [" + record.getId() + "]");
}
merge(pi, record);
pi.update();
if (schedule) {
// For schedule we don't need to persist a processLog
return;
}
ProcessLog processLog = record.getProcessLog();
if (record.getId() != null && processLog != null && processLog.getUuid() != null) {
String uuid = processLog.getUuid();
Map<String, Object> log = convertToMap(record, processLog);
int result = create()
.update(PROCESS_EXECUTION)
.set(PROCESS_EXECUTION.LOG, log)
.where(PROCESS_EXECUTION.UUID.eq(uuid))
.execute();
if (result == 0) {
create()
.insertInto(PROCESS_EXECUTION, PROCESS_EXECUTION.PROCESS_INSTANCE_ID, PROCESS_EXECUTION.UUID,
PROCESS_EXECUTION.LOG, PROCESS_EXECUTION.CREATED)
.values(record.getId(), uuid, log, new Timestamp(System.currentTimeMillis()))
.execute();
}
}
}
protected void merge(ProcessInstanceRecord pi, ProcessRecord record) {
pi.setStartTime(toTimestamp(record.getStartTime()));
pi.setEndTime(toTimestamp(record.getEndTime()));
pi.setResult(ObjectUtils.toString(record.getResult(), null));
pi.setExitReason(ObjectUtils.toString(record.getExitReason(), null));
pi.setPhase(ObjectUtils.toString(record.getPhase(), null));
pi.setStartProcessServerId(record.getStartProcessServerId());
pi.setRunningProcessServerId(record.getRunningProcessServerId());
pi.setExecutionCount(record.getExecutionCount());
pi.setRunAfter(record.getRunAfter());
if (record.getAccountId() instanceof Number) {
pi.setAccountId(((Number)record.getAccountId()).longValue());
}
pi.setPriority(record.getPriority());
pi.setResourceType(record.getResourceType());
pi.setResourceId(record.getResourceId());
pi.setProcessName(record.getProcessName());
pi.setData(record.getData());
pi.setPriority(record.getPriority());
if (ExitReason.RETRY_EXCEPTION == record.getExitReason() || record.getRunAfter() == null) {
pi.setRunAfter(new Date(System.currentTimeMillis()-300000));
}
}
protected Timestamp toTimestamp(Date date) {
return date == null ? null : new Timestamp(date.getTime());
}
protected <T> T convertToType(Object obj, Class<T> type) {
return jsonMapper.convertValue(obj, type);
}
@SuppressWarnings("unchecked")
protected Map<String, Object> convertToMap(ProcessRecord record, ProcessLog obj) {
if (obj == null)
return null;
try {
String stringData = jsonMapper.writeValueAsString(obj);
if (stringData.length() > 1000000) {
log.error("Process log is too long for id [{}] truncating executions : {}", record.getId(), stringData);
obj.getExecutions().clear();
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
return jsonMapper.convertValue(obj, Map.class);
}
@Override
public ProcessInstanceReference loadReference(Long id) {
ProcessInstance record = objectManager.loadResource(ProcessInstance.class, id);
if (record == null || record.getEndTime() != null) {
return null;
}
ProcessInstanceReference ref = new ProcessInstanceReference();
ref.setName(record.getProcessName());
ref.setPriority(record.getPriority() == null ? 0 : record.getPriority());
ref.setProcessId(record.getId());
return ref;
}
}