package com.qprogramming.tasq.task; import com.qprogramming.tasq.account.Account; import com.qprogramming.tasq.account.AccountService; import com.qprogramming.tasq.account.LastVisitedService; import com.qprogramming.tasq.agile.AgileService; import com.qprogramming.tasq.agile.Release; import com.qprogramming.tasq.agile.Sprint; import com.qprogramming.tasq.manage.AppService; import com.qprogramming.tasq.projects.Project; import com.qprogramming.tasq.support.PeriodHelper; import com.qprogramming.tasq.support.ResultData; import com.qprogramming.tasq.support.Utils; import com.qprogramming.tasq.task.comments.Comment; import com.qprogramming.tasq.task.comments.CommentService; import com.qprogramming.tasq.task.link.TaskLinkService; import com.qprogramming.tasq.task.watched.WatchedTaskService; import com.qprogramming.tasq.task.worklog.LogType; import com.qprogramming.tasq.task.worklog.WorkLogService; import org.apache.commons.io.FileUtils; import org.hibernate.Hibernate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.stereotype.Service; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Service public class TaskService { private static final Logger LOG = LoggerFactory.getLogger(TaskService.class); private TaskRepository taskRepo; private AppService appSrv; private AccountService accountSrv; private TaskLinkService linkSrv; private WorkLogService wlSrv; private CommentService comSrv; private MessageSource msg; private WatchedTaskService watchSrv; private LastVisitedService visitedSrv; @PersistenceContext private EntityManager entityManager; @Autowired public TaskService(TaskRepository taskRepo, AppService appSrv, AccountService accountSrv, MessageSource msg, WorkLogService wlSrv, CommentService comSrv, TaskLinkService linkSrv, WatchedTaskService watchSrv, LastVisitedService visitedSrv) { this.taskRepo = taskRepo; this.appSrv = appSrv; this.accountSrv = accountSrv; this.msg = msg; this.linkSrv = linkSrv; this.wlSrv = wlSrv; this.comSrv = comSrv; this.watchSrv = watchSrv; this.visitedSrv = visitedSrv; } public Task save(Task task) { return taskRepo.save(task); } public List<Task> findAllByProject(Project project) { return taskRepo.findAllByProjectAndParentIsNull(project); } public List<Task> findAll() { return taskRepo.findAll(); } public List<Task> findByProjectAndState(Project project, TaskState state) { return taskRepo.findByProjectAndStateAndParentIsNull(project, state); } public List<Task> findByProjectAndOpen(Project project) { return taskRepo.findByProjectAndStateNotAndParentIsNull(project, TaskState.CLOSED); } public List<Task> findAllWithoutRelease(Project project) { return taskRepo.findByProjectAndParentIsNullAndReleaseIsNull(project); } public List<Task> findAllToRelease(Project project) { return taskRepo.findByProjectAndStateAndParentIsNullAndReleaseIsNull(project, TaskState.CLOSED); } /** * @param id * @return */ public Task findById(String id) { return taskRepo.findById(id); } public List<Task> findByAssignee(Account assignee) { return taskRepo.findByAssignee(assignee); } public List<Task> findAllByUser(Account account) { return taskRepo.findAllByProjectParticipants_Id(account.getId()); } public void delete(Task task) { taskRepo.delete(task); } public List<Task> findAllBySprint(Sprint sprint) { return taskRepo.findByProjectAndSprintsId(sprint.getProject(), sprint.getId()); } public List<Task> findAllBySprintId(Project project, Long sprintId) { return taskRepo.findByProjectAndSprintsId(project, sprintId); } public List<Task> findAllByRelease(Release release) { return taskRepo.findByProjectAndRelease(release.getProject(), release); } public List<Task> findAllByRelease(Project project, Release release) { return taskRepo.findByProjectAndRelease(project, release); } public List<Task> findSubtasksForProject(Project project) { if (project == null) { return taskRepo.findByParentIsNotNull(); } return taskRepo.findByProjectAndParentIsNotNull(project); } public List<Task> findSubtasks(String taskID) { return taskRepo.findByParent(taskID); } public List<Task> findSubtasks(Task task) { return findSubtasks(task.getId()); } public void deleteAll(List<Task> tasks) { taskRepo.delete(tasks); } public String getTaskDirectory(Task task) { String dirPath = getTaskDir(task); File dir = new File(dirPath); if (!dir.exists()) { dir.mkdirs(); dir.setWritable(true, false); dir.setReadable(true, false); } return dirPath; } public String getTaskDir(Task task) { return appSrv.getProperty(AppService.TASQROOTDIR) + File.separator + task.getProject().getProjectId() + File.separator + task.getId(); } public List<Task> finAllById(List<String> taskIDs) { return taskRepo.findByIdIn(taskIDs); } public List<Task> save(List<Task> taskList) { return taskRepo.save(taskList); } public List<Task> findAllByProjectId(Long project) { return taskRepo.findByProjectId(project); } /** * converts to DisplayTask * * @param list * @param tags if tags should be included (!requires transaction ) * @return */ public List<DisplayTask> convertToDisplay(List<Task> list, boolean tags) { List<DisplayTask> resultList = new LinkedList<DisplayTask>(); for (Task task : list) { DisplayTask displayTask = new DisplayTask(task); if (tags) { Hibernate.initialize(task.getTags()); displayTask.setTagsFromTask(task.getTags()); } resultList.add(displayTask); } return resultList; } /** * Returns all tasks with given tag * * @param name * @return */ public List<Task> findByTag(String name) { return taskRepo.findByTagsName(name); } // @Transactional public Set<Sprint> getTaskSprints(String id) { Task task = taskRepo.findById(id); Hibernate.initialize(task.getSprints()); return task.getSprints(); } public List<Task> findBySpecification(TaskFilter filter) { return taskRepo.findAll(new TaskSpecification(filter)); } /** * Creates subtask for which task is parent * Must be run within transactional block * * @param project task project * @param parentTask parent task * @param subTask new subtask */ public Task createSubTask(Project project, Task parentTask, Task subTask) { int taskCount = parentTask.getSubtasks(); taskCount++; String taskID = createSubId(parentTask.getId(), String.valueOf(taskCount)); subTask.setId(taskID); subTask.setParent(parentTask.getId()); subTask.setProject(project); parentTask.addSubTask(); Hibernate.initialize(parentTask.getSubtasks()); Task subtask = save(subTask); save(parentTask); return subtask; } public String createSubId(String id, String subId) { return id + "/" + subId; } private void clearActiveTimersForTask(Task task) { List<Account> withActiveTask = accountSrv.findAllWithActiveTask(task.getId()); withActiveTask.stream().forEach(Account::clearActiveTask); accountSrv.update(withActiveTask); } /** * Deletes task with all it's subtasks and relations. It can be forced to zero all active tasks and just removed * or if force is set to false , all tasks and subtaks will be checked if somebody is not working on them * * @param task task to be deleted * @param force if set to true, no check of active tasks will be made * @return {@link com.qprogramming.tasq.support.ResultData} */ public ResultData deleteTask(Task task, boolean force) { ResultData resultData; if (force) { clearActiveTimersForTask(task); } else { resultData = checkTaskCanOperated(task, true); if (resultData.code.equals(ResultData.Code.ERROR)) { return resultData; } } //check it's subtasks List<Task> subtasks = findSubtasks(task.getId()); for (Task subtask : subtasks) { if (force) { clearActiveTimersForTask(subtask); } else { resultData = checkTaskCanOperated(subtask, true); if (ResultData.Code.ERROR.equals(resultData.code)) { return resultData; } } } //all ok proceed with removal subtasks.forEach(this::removeTaskRelations); deleteAll(subtasks); try { deleteFiles(task); } catch (IOException e) { String message = msg.getMessage("error.task.delete.files", new Object[]{task.getId(), e.getMessage()}, Utils.getCurrentLocale()); LOG.error(message + " Exception {}", e.getMessage()); LOG.debug("{}", e); return new ResultData(ResultData.Code.ERROR, message); } removeTaskRelations(task); Task purged = save(purgeTask(task)); delete(purged); return new ResultData(ResultData.Code.OK, null); } public ResultData checkTaskCanOperated(Task task, boolean remove) { List<Account> accounts = accountSrv.findAllWithActiveTask(task.getId()); if (!accounts.isEmpty()) { Account currentAccount = Utils.getCurrentAccount(); if (accounts.size() > 1 || !accounts.get(0).equals(currentAccount)) { return new ResultData(ResultData.Code.ERROR, msg.getMessage("task.changeState.change.working", new Object[]{task.getId(), String.join(",", accounts.stream().map(Account::toString).collect(Collectors.toList()))}, Utils.getCurrentLocale())); } if (remove) { currentAccount.clearActiveTask(); accountSrv.update(currentAccount); } } return new ResultData(ResultData.Code.OK, null); } public void deleteFiles(Task task) throws IOException { File folder = new File(getTaskDirectory(task)); FileUtils.deleteDirectory(folder); } /** * Checks if state should be changed to ongoing and saves task * * @param task * @return */ public Task checkStateAndSave(Task task) { if (task.getState().equals(TaskState.TO_DO)) { task.setState(TaskState.ONGOING); changeState(TaskState.TO_DO, TaskState.ONGOING, task); } return save(task); } /** * private method to remove task and all potential links * * @param task * @return */ public void removeTaskRelations(Task task) { linkSrv.deleteTaskLinks(task); task.setOwner(null); task.setAssignee(null); task.setProject(null); task.setTags(null); wlSrv.deleteTaskWorklogs(task); Set<Comment> comments = comSrv.findByTaskIdOrderByDateDesc(task.getId()); comSrv.delete(comments); watchSrv.deleteWatchedTask(task.getId()); visitedSrv.delete(task); } /** * Adds event about state changed * * @param newState * @param oldState * @param task */ public void changeState(TaskState oldState, TaskState newState, Task task) { wlSrv.addActivityLog(task, Utils.changedFromTo(oldState.getDescription(), newState.getDescription()), LogType.STATUS); } /** * Nuke Task - set everything to null */ private Task purgeTask(Task task) { Task zerotask = new Task(); zerotask.setId(task.getId()); return zerotask; } /** * It's crucial that passed task MUST be detached from session , otherwise it's original value will be overwritten * * @param task * @param subtasks * @return */ public Task addSubtaskTimers(Task task, List<Task> subtasks) { getEntitymanager().detach(task); for (Task subtask : subtasks) { task.setEstimate(PeriodHelper.plusPeriods(task.getRawEstimate(), subtask.getRawEstimate())); task.setLoggedWork(PeriodHelper.plusPeriods(task.getRawLoggedWork(), subtask.getRawLoggedWork())); task.setRemaining(PeriodHelper.plusPeriods(task.getRawRemaining(), subtask.getRawRemaining())); } return task; } /** * It's crucial that passed task MUST be detached from session , otherwise it's original value will be overwritten * * @param task * @return */ public Task addSubtaskTimers(Task task) { List<Task> subtasks = findSubtasks(task); return addSubtaskTimers(task, subtasks); } protected EntityManager getEntitymanager() { return entityManager; } public String printID(String taskID) { return "[ " + taskID + " ]"; } }