package ru.hflabs.rcd.task; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import ru.hflabs.rcd.event.task.TaskEvent; import ru.hflabs.rcd.event.task.TaskExecutionEvent; import ru.hflabs.rcd.event.task.TaskProgressEvent; import ru.hflabs.rcd.model.task.*; import ru.hflabs.rcd.service.task.ITaskPerformer; import ru.hflabs.rcd.service.task.ITaskProgress; import ru.hflabs.util.io.IOUtils; import java.lang.reflect.UndeclaredThrowableException; import java.util.Date; import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static ru.hflabs.rcd.accessor.Accessors.injectId; /** * Класс <class>TaskWorker</class> реализует обертку над выполняемой задачей с предоставлением доступа к результату выполнения * * @see ru.hflabs.rcd.model.task.TaskDescriptor * @see ru.hflabs.rcd.model.task.TaskResult * @see RunnableFuture */ public class TaskWorker implements RunnableFuture<TaskResult>, ITaskProgress { private final Logger LOG = LoggerFactory.getLogger(getClass()); /** Дескриптор выполнения задачи */ private final TaskDescriptor descriptor; /** Результат выполнения задачи */ private final TaskResult result; /** Текущий статус выполнения задачи */ private volatile TaskExecutionStatus status; /** Прогресс выполнения */ private volatile TaskProgress progress; /** Исполнитель задачи */ private final ITaskPerformer performer; /** Сервис публикации событий */ private final ApplicationEventPublisher eventPublisher; /** Блокировка смены статуса */ private final Lock writeLock; private final Lock readLock; /** Блокировка при выполнении задачи */ private final CountDownLatch executionLock; public TaskWorker(String owner, String author, TaskDescriptor descriptor, ITaskPerformer performer, ApplicationEventPublisher eventPublisher) { // initialize variables this.descriptor = descriptor; this.performer = performer; this.eventPublisher = eventPublisher; // locks final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); this.writeLock = lock.writeLock(); this.readLock = lock.readLock(); this.executionLock = new CountDownLatch(1); // fill variables this.result = injectId(new TaskResult(), descriptor.getId()); this.result.setDescriptorId(descriptor.getId()); this.result.setOwner(owner); this.result.setAuthor(author); this.result.setParameters(descriptor.getParameters()); this.result.setRegistrationDate(new Date()); this.result.setStatus(TaskResultStatus.UNKNOWN); // change state changeState(new TaskProgress(TaskProgress.PENDING_STEP), TaskExecutionStatus.RUNNING); } /** * Выполняет логирование и публикацию события задачи * * @param event событие задачи * @param needLogging флаг необходимости логирования */ private void doPublishEvent(TaskEvent event, boolean needLogging) { if (needLogging) { LOG.info("Task {}", event.identity()); } eventPublisher.publishEvent(event); } /** * Публикует событие изменения статуса выполнения задачи * * @param targetState обновленный статус выполнения */ private void changeState(TaskExecutionStatus targetState) { writeLock.lock(); try { status = targetState; doPublishEvent(new TaskExecutionEvent(this, descriptor.getId(), performer.retrieveName(), targetState), false); } finally { writeLock.unlock(); } } /** * Публикует событие изменения прогресса выполнения задачи * * @param targetProgress обновленный прогресс выполнения */ private void changeState(TaskProgress targetProgress) { writeLock.lock(); try { progress = targetProgress; doPublishEvent(new TaskProgressEvent(this, descriptor.getId(), performer.retrieveName(), targetProgress), true); } finally { writeLock.unlock(); } } /** * Публикует событие изменения статуса и прогресса выполнения задачи * * @param targetState обновленный статус и прогресса выполнения */ private void changeState(TaskProgress targetProgress, TaskExecutionStatus targetState) { writeLock.lock(); try { changeState(targetProgress); changeState(targetState); } finally { writeLock.unlock(); } } @Override public boolean isTaskCanceled() { return isCancelled(); } @Override public void changeTaskProgress(String performerName, TaskProgress taskProgress) { if (performer.retrieveName().equals(performerName)) { changeState(taskProgress); } } /** * Устанавливает параметры запуска задачи * * @see #run() */ private void startTask() { writeLock.lock(); try { result.setStartDate(new Date()); changeState(new TaskProgress(TaskProgress.EXECUTING_STEP)); } finally { writeLock.unlock(); } } /** * Устанавливает параметры завершения задачи * * @param results результат задачи * @param resultStatus статус завершения * @param exception исключения, которое произошло в процессе выполнения */ private void endTask(Map<String, Object> results, TaskResultStatus resultStatus, Throwable exception) { writeLock.lock(); try { result.setContent(results); result.setStatus(resultStatus); if (exception != null) { result.setErrorMessage(StringUtils.abbreviate(exception.getMessage(), TaskResult.ERROR_MESSAGE_MAX_SIZE - 3)); } result.setEndDate(new Date()); executionLock.countDown(); changeState(null, TaskExecutionStatus.READY); } finally { writeLock.unlock(); } } @Override public void run() { // Устанавливаем системные параметры запуска startTask(); // Выполняем задачу Map<String, Object> results = null; TaskResultStatus resultStatus; Throwable cause = null; try { results = performer.performTask(this, result.getParameters()); resultStatus = (isCancelled()) ? TaskResultStatus.CANCELED : TaskResultStatus.FINISHED; } catch (Throwable th) { resultStatus = TaskResultStatus.ERROR; cause = th; LOG.error(String.format("Task '%s[%s]' error: %s", descriptor.getId(), performer.retrieveName(), cause.getMessage()), cause); } // Устанавливаем системные параметры завершения endTask(results, resultStatus, cause); } /** * Возвращает декоратор выполняемой задачи * * @return Возвращает декоратор выполняемой задачи */ public TaskExecution getTaskExecution() { readLock.lock(); try { return new TaskExecution(descriptor, IOUtils.deepClone(result), IOUtils.deepClone(status), IOUtils.deepClone(progress)); } finally { readLock.unlock(); } } /** * Возвращает текущий результат выполнения задачи * * @return Возвращает текущий результат выполнения задачи * @see #get() */ public TaskResult getQuietly() { try { return get(); } catch (InterruptedException | ExecutionException ex) { throw new UndeclaredThrowableException(ex); } } @Override public TaskResult get() throws InterruptedException, ExecutionException { executionLock.await(); return result; } @Override public TaskResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (!executionLock.await(timeout, unit)) { throw new TimeoutException(String.format("Task '%s[%s]' hasn't completed", performer.retrieveName(), descriptor.getId())); } return result; } @Override public boolean isCancelled() { readLock.lock(); try { return TaskExecutionStatus.INTERRUPTING.equals(status); } finally { readLock.unlock(); } } @Override public boolean cancel(boolean mayInterruptIfRunning) { if (!isCancelled()) { changeState(TaskExecutionStatus.INTERRUPTING); return true; } return false; } @Override public boolean isDone() { readLock.lock(); try { return TaskExecutionStatus.READY.equals(status); } finally { readLock.unlock(); } } }