package ru.hflabs.rcd.web.controller.task; import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import ru.hflabs.rcd.model.Comparators; import ru.hflabs.rcd.model.criteria.FilterCriteria; import ru.hflabs.rcd.model.task.*; import ru.hflabs.rcd.service.IDocumentService; import ru.hflabs.rcd.service.IFilterService; import ru.hflabs.rcd.service.IStorageService; import ru.hflabs.rcd.service.task.ITaskLauncher; import ru.hflabs.rcd.web.controller.ControllerTemplate; import javax.annotation.Resource; import javax.swing.*; import java.text.MessageFormat; import java.util.*; import static ru.hflabs.rcd.accessor.Accessors.injectId; import static ru.hflabs.rcd.model.CriteriaUtils.createCriteriaByIDs; import static ru.hflabs.rcd.model.ModelUtils.ID_FUNCTION; import static ru.hflabs.rcd.service.ServiceUtils.extractSingleDocument; /** * Класс <class>TaskController</class> реализует контроллер управления асинхронными задачами * * @see TaskDefinition * @see TaskDescriptor * @see TaskResult */ @Controller(TaskController.MAPPING_URI + TaskController.NAME_POSTFIX) @RequestMapping(TaskController.MAPPING_URI + TaskController.DATA_URI) public class TaskController extends ControllerTemplate { public static final String MAPPING_URI = "tasks"; /** Репозиторий предопределенных дескрипторов */ @Resource(name = "taskDefinitionRepository") private IStorageService<TaskDefinition> taskDefinitionRepository; /** Репозиторий сохраненных дескрипторов */ @Resource(name = "taskDescriptorRepository") private IDocumentService<TaskDescriptor> taskDescriptorRepository; /** Репозиторий выполненных задач */ @Resource(name = "taskResultRepository") private IFilterService<TaskResult> taskResultRepository; /** Сервис выполнения задач */ @Resource(name = "taskLauncher") private ITaskLauncher taskLauncher; /** * Выполняет локализацию прогресса задачи * * @param progress прогресс выполнения задачи * @param locale целевая локализация * @return Возвращает локализованное значение прогресса */ protected TaskProgress localizedTaskProgress(TaskProgress progress, Locale locale) { return progress != null ? new TaskProgress( progress.getPercent(), messageSource.getMessage(progress.getCode(), progress.getArguments(), MessageFormat.format(progress.getStep(), progress.getArguments()), locale), progress.getCode(), progress.getArguments() ) : null; } /** * Выполняет локализацию прогресса задачи * * @param execution декоратор выполнения задачи * @param locale целевая локализация * @return Возвращает локализованное значение декоратора */ protected TaskExecution localizedTaskProgress(TaskExecution execution, Locale locale) { execution.setProgress(localizedTaskProgress(execution.getProgress(), locale)); return execution; } @RequestMapping(value = "/model", method = RequestMethod.GET) @ResponseBody public Collection<TaskDefinition> createModel() { return taskDefinitionRepository.getAll(); } @RequestMapping(value = "/", method = RequestMethod.POST) @ResponseBody public TaskDescriptor createTask(@RequestBody TaskDescriptor descriptor) { return createSingleDocument(taskDescriptorRepository, descriptor, true); } @RequestMapping(value = "/{id}", method = RequestMethod.PUT) @ResponseBody public TaskDescriptor updateTask(@PathVariable String id, @RequestBody TaskDescriptor descriptor) { return updateSingleDocument(taskDescriptorRepository, injectId(descriptor, id), true); } @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @ResponseBody public void closeTask(@PathVariable String id) { taskDescriptorRepository.closeByIDs(Sets.newHashSet(id)); } @RequestMapping(value = "/{id}/execute", method = RequestMethod.PUT) @ResponseBody public TaskExecution executeTask(@PathVariable String id, Locale locale) { return localizedTaskProgress(taskLauncher.submitAsyncTask(id), locale); } @RequestMapping(value = "/{id}/cancel", method = RequestMethod.PUT) @ResponseBody public TaskExecution cancelTask(@PathVariable String id, Locale locale) { // Выполняем отмену TaskExecution execution = extractSingleDocument(taskLauncher.cancelTask(Sets.newHashSet(id)), null); // Если задача еще выполнялась, то возвращаем результат if (execution != null) { return localizedTaskProgress(execution, locale); } // Если задачи не найдено, то пытаемся определить ее последнее выполнение return findLatestTaskExecution(id); } /** * Возвращает последний результат выполнения задачи по ее идентификатору * * @param id идентификатор выполнения задачи * @return Возвращает последний результат выполнения задачи по ее идентификатору или <code>NULL</code>, если задача не выполнялась */ private TaskResult findLatestTaskResult(String id) { return extractSingleDocument( taskResultRepository.findByCriteria( createCriteriaByIDs(TaskResult.DESCRIPTOR_ID, id) .injectSort(TaskResult.REGISTRATION_DATE, SortOrder.DESCENDING) .injectCount(1), false ).getResult(), null); } /** * Возвращает декоратор задачи с привязкой к ее последнему выполнению * * @param id идентификатор выполнения задачи * @return Возвращает последний результат выполнения задачи по ее идентификатору или <code>NULL</code>, если задача не выполнялась */ private TaskExecution findLatestTaskExecution(String id) { // Если задачи не найдено, то пытаемся определить дескриптор задачи TaskDescriptor taskDescriptor = taskDescriptorRepository.findByID(id, true, false); // Получаем последнее выпонение задачи TaskResult taskResult = findLatestTaskResult(id); // Возвращаем результат выполнения задачи return new TaskExecution(taskDescriptor, taskResult, TaskExecutionStatus.READY, null); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) @ResponseBody public TaskExecution getTask(@PathVariable String id, Locale locale) { // Получаем существующую выполняемую задачу TaskExecution execution = taskLauncher.findByID(id, true, true); // Проверяем, что задача выполняется if (execution != null) { return localizedTaskProgress(execution, locale); } // Если задачи не найдено, то пытаемся определить ее последнее выполнение return findLatestTaskExecution(id); } @RequestMapping(value = "/", method = RequestMethod.GET) @ResponseBody public Collection<TaskExecution> getTasks(Locale locale) { // Получаем сохраненные дескрипторы задач final Map<String, TaskDescriptor> descriptors = Maps.newLinkedHashMap( Maps.uniqueIndex(taskDescriptorRepository.findAllByCriteria(new FilterCriteria(), true), ID_FUNCTION) ); // Убираем дескрипторы выполняющихся задач final Collection<TaskExecution> currentExecutions = getExecutingTasks(locale); for (TaskExecution execution : currentExecutions) { descriptors.remove(execution.getId()); } // Выполняем формирование декораторов для выполненных задач final List<TaskExecution> result = new LinkedList<>(currentExecutions); for (TaskDescriptor taskDescriptor : descriptors.values()) { TaskResult executed = findLatestTaskResult(taskDescriptor.getId()); result.add(new TaskExecution(taskDescriptor, executed, TaskExecutionStatus.READY, null)); } Collections.sort(result, Comparators.IDENTIFYING_COMPARATOR); return result; } @RequestMapping(value = "/executing", method = RequestMethod.GET) @ResponseBody public Collection<TaskExecution> getExecutingTasks(final Locale locale) { List<TaskExecution> result = Lists.newArrayList( Collections2.transform(taskLauncher.findExecuted(), new Function<TaskExecution, TaskExecution>() { @Override public TaskExecution apply(TaskExecution input) { return localizedTaskProgress(input, locale); } }) ); Collections.sort(result, Comparators.IDENTIFYING_COMPARATOR); return result; } }