/**
* Copyright (C) 2015 Orange
* 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 com.francetelecom.clara.cloud.commons.tasks;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Common cloud project Task component. A task is a long running activity,
* internal to cloud projet and / or relating to external long running activity
*/
public class TaskStatus implements Serializable {
private static final long serialVersionUID = -6895838149206097622L;
private static Logger logger = LoggerFactory.getLogger(TaskStatus.class.getName());
// TODO : use a generics to keep orig taskStatus ?
private final long taskId;
private TaskStatusEnum taskStatus = TaskStatusEnum.TRANSIENT;
private final List<TaskStatus> subtasks = new ArrayList<TaskStatus>();
private static final NumberFormat formatter = new DecimalFormat("000");
private int maxPercent = 100;
/** Start time of the task, -1 if not set (not started) */
private long startTime = -1;
/** End time of the task, -1 if not set (not ended) */
private long endTime = -1;
/** Suggested timeout in seconds. If 0 then no timeout is suggested. */
private long suggestedTimeout = 0L;
/** Indicates the last update date of this status */
private long lastUpdate = -1;
/** Title the task : should be set once, at the beginning of the task */
private String title = "";
/** Subtitle the task : describe what is currently done */
private String subtitle = "";
/**
* Indicates the percentage of the task: -1 mean that it is not known,
* otherwise it a value between 0 and 100
*/
private int percent = -1;
/**
* Error message if any
*/
private String errorMessage = "";
/**
* Empty task status constructor, for new TaskStatus
*/
public TaskStatus() {
this(-1);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (endTime ^ (endTime >>> 32));
result = prime * result + ((errorMessage == null) ? 0 : errorMessage.hashCode());
result = prime * result + (int) (lastUpdate ^ (lastUpdate >>> 32));
result = prime * result + maxPercent;
result = prime * result + percent;
result = prime * result + (int) (startTime ^ (startTime >>> 32));
result = prime * result + ((subtitle == null) ? 0 : subtitle.hashCode());
result = prime * result + (int) (suggestedTimeout ^ (suggestedTimeout >>> 32));
result = prime * result + (int) (taskId ^ (taskId >>> 32));
result = prime * result + ((taskStatus == null) ? 0 : taskStatus.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TaskStatus other = (TaskStatus) obj;
if (endTime != other.endTime)
return false;
if (errorMessage == null) {
if (other.errorMessage != null)
return false;
} else if (!errorMessage.equals(other.errorMessage))
return false;
if (lastUpdate != other.lastUpdate)
return false;
if (maxPercent != other.maxPercent)
return false;
if (percent != other.percent)
return false;
if (startTime != other.startTime)
return false;
if (subtitle == null) {
if (other.subtitle != null)
return false;
} else if (!subtitle.equals(other.subtitle))
return false;
if (suggestedTimeout != other.suggestedTimeout)
return false;
if (taskId != other.taskId)
return false;
if (taskStatus != other.taskStatus)
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
/**
* Basic task status constructor
*
* @param taskId
* ID of the task status
*/
public TaskStatus(long taskId) {
this.taskId = taskId;
this.startTime = System.currentTimeMillis();
this.lastUpdate = System.currentTimeMillis();
}
/**
* Copy constructor
*
* @param status
* Status to copy
*/
public TaskStatus(TaskStatus status) {
this.taskId = status.taskId;
this.startTime = status.startTime;
this.endTime = status.endTime;
this.taskStatus = status.taskStatus;
this.title = status.title;
this.subtitle = status.subtitle;
this.percent = status.percent;
this.errorMessage = status.errorMessage;
this.lastUpdate = System.currentTimeMillis();
this.maxPercent = status.maxPercent;
this.suggestedTimeout = status.suggestedTimeout;
for (TaskStatus subtask : status.subtasks) {
this.addSubtask(new TaskStatus(subtask));
}
}
/**
* Enables copy constructor to instanciate subclasses of TaskStatus as subtasks
*/
public interface SubTaskFactory {
TaskStatus createSubTask(TaskStatus subtask);
}
/**
* Copy constructor for subclassed substasks
*
* @param status
* Status to copy
*/
public TaskStatus(TaskStatus status, SubTaskFactory subTaskFactory) {
this.taskId = status.taskId;
this.startTime = status.startTime;
this.endTime = status.endTime;
this.taskStatus = status.taskStatus;
this.title = status.title;
this.subtitle = status.subtitle;
this.percent = status.percent;
this.errorMessage = status.errorMessage;
this.lastUpdate = System.currentTimeMillis();
this.maxPercent = status.maxPercent;
this.suggestedTimeout = status.suggestedTimeout;
for (TaskStatus subtask : status.subtasks) {
this.addSubtask(subTaskFactory.createSubTask(subtask));
}
}
/**
* Update status depending on sub tasks
*/
public void update() {
if (subtasks.size() > 0) {
// If there are subtask, then update fields depending on them
int totalPercent = 0;
int totalMaxPercent = 0;
long endTime = 0;
boolean allFinishedOk = true, oneStarted = false, oneFailed = false;
for (TaskStatus subtask : subtasks) {
totalMaxPercent += subtask.getMaxPercent();
if (subtask.getPercent() > 0) {
totalPercent += (subtask.getPercent() * subtask.getMaxPercent()) / 100;
}
if (subtask.getEndTime() > getEndTime()) {
endTime = subtask.getEndTime();
}
allFinishedOk = allFinishedOk && subtask.getTaskStatus() == TaskStatusEnum.FINISHED_OK;
if (subtask.getTaskStatus() != TaskStatusEnum.TRANSIENT) {
oneStarted = true;
}
if (subtask.getTaskStatus() == TaskStatusEnum.STARTED) {
setSubtitle(subtask.getSubtitle());
} else if (subtask.getTaskStatus() == TaskStatusEnum.FINISHED_FAILED) {
oneFailed = true;
setTaskStatus(TaskStatusEnum.FINISHED_FAILED);
setErrorMessage(subtask.getErrorMessage());
break;
}
}
if (getTaskStatus() != TaskStatusEnum.FINISHED_FAILED) {
setPercent((totalPercent * 100) / totalMaxPercent);
}
if (getTaskStatus() != TaskStatusEnum.FINISHED_FAILED && allFinishedOk) {
setTaskStatus(TaskStatusEnum.FINISHED_OK);
setPercent(100);
setSubtitle("");
setEndTime(endTime);
} else if (getTaskStatus() != TaskStatusEnum.FINISHED_FAILED && oneStarted && !oneFailed) {
setTaskStatus(TaskStatusEnum.STARTED);
}
}
}
public static void displayTaskStatus(TaskStatus status, int offset) {
StringBuffer buf = new StringBuffer(offset);
for (int i = 0; i < offset; i++) {
buf.append(" ");
}
if (status.getPercent() < 0) {
buf.append(" - ]");
} else {
buf.append(formatter.format(status.getPercent()));
buf.append("%]");
}
switch (status.getTaskStatus()) {
case FINISHED_FAILED:
buf.append(" (*KO) ");
break;
case FINISHED_OK:
buf.append(" (+OK) ");
break;
case STARTED:
buf.append(" (RUN) ");
break;
case TRANSIENT:
buf.append(" (---) ");
break;
}
buf.append(status.getTitle());
if (status.getSubtitle() != null && status.getSubtitle().length() > 0) {
buf.append(" : ");
buf.append(status.getSubtitle());
}
if (status.getTaskStatus() == TaskStatusEnum.FINISHED_FAILED) {
buf.append(" ** ");
buf.append(status.getErrorMessage());
}
logger.info(buf.toString());
for (TaskStatus subtask : status.listSubtasks()) {
displayTaskStatus(subtask, offset + 3);
}
}
public TaskStatusEnum getTaskStatus() {
return taskStatus;
}
public void setTaskStatus(TaskStatusEnum taskStatus) {
this.taskStatus = taskStatus;
this.lastUpdate = System.currentTimeMillis();
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
this.lastUpdate = System.currentTimeMillis();
}
public long getEndTime() {
return endTime;
}
public void setEndTime(long endTime) {
this.endTime = endTime;
this.lastUpdate = System.currentTimeMillis();
}
public long getSuggestedTimeout() {
return suggestedTimeout;
}
public void setSuggestedTimeout(long suggestedTimeout) {
this.suggestedTimeout = suggestedTimeout;
this.lastUpdate = System.currentTimeMillis();
}
public long getTaskId() {
return taskId;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
this.lastUpdate = System.currentTimeMillis();
}
public String getErrorMessage() {
return errorMessage;
}
/**
* Returns complete if finished OK or failed
*
* @return true if finished OK or failed
*/
public boolean isComplete() {
return (hasFailed() || hasSucceed());
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
this.lastUpdate = System.currentTimeMillis();
}
public String getSubtitle() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
this.lastUpdate = System.currentTimeMillis();
}
public int getPercent() {
return percent;
}
public void setPercent(int percent) {
this.percent = percent;
this.lastUpdate = System.currentTimeMillis();
}
public long getLastUpdate() {
return lastUpdate;
}
public void addSubtask(TaskStatus status) {
subtasks.add(status);
update();
}
public int getMaxPercent() {
return maxPercent;
}
public void setMaxPercent(int maxPercent) {
this.maxPercent = maxPercent;
}
public List<TaskStatus> listSubtasks() {
return Collections.unmodifiableList(subtasks);
}
@Override
public String toString() {
return "TaskStatus [taskId=" + taskId + ", taskStatus=" + taskStatus + ", startTime=" + startTime + ", endTime=" + endTime + ", lastUpdate="
+ lastUpdate + ", title=" + title + ", subtitle=" + subtitle + ", percent=" + percent + ", errorMessage=" + errorMessage + "]";
}
//~ status change helpers
/**
* help to change a task status as 'STARTED'
* change the title, startTime (to NOW), an status
* @param newTitle the new title to set
*/
public void setAsStarted(String newTitle) {
setTitle(newTitle);
setStartTime(System.currentTimeMillis());
setTaskStatus(TaskStatusEnum.STARTED);
}
/**
* help to change a task status as 'FINISHED_OK'
* endTime (to NOW), an status
*/
public void setAsFinishedOk() {
setPercent(100);
setEndTime(System.currentTimeMillis());
setTaskStatus(TaskStatusEnum.FINISHED_OK);
}
/**
* help to change a task status as 'FINISHED_FAILED'
* setErrorMessage, endTime (to NOW), an status
* @param errorMessage error message to set on the taskStatus
*/
public void setAsFinishedFailed(String errorMessage) {
setEndTime(System.currentTimeMillis());
setTaskStatus(TaskStatusEnum.FINISHED_FAILED);
setErrorMessage(errorMessage);
}
public void progress(int percent, String subtitle) {
setPercent(percent);
setSubtitle(subtitle);
logger.debug(subtitle);
}
/**
* @return true if task has succeed
*/
public boolean hasSucceed() {
return TaskStatusEnum.FINISHED_OK.equals(taskStatus);
}
/**
* @return true if task has failed
*/
public boolean hasFailed() {
return TaskStatusEnum.FINISHED_FAILED.equals(taskStatus);
}
/**
* @return true if task has started
*/
public boolean isStarted() {
return TaskStatusEnum.STARTED.equals(taskStatus);
}
}