/** * Copyright 2010 JBoss Inc * * 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 org.drools.task.service; import org.drools.RuleBase; import org.drools.StatefulSession; import org.drools.eventmessaging.EventKeys; import org.drools.task.*; import org.drools.task.query.DeadlineSummary; import org.drools.task.query.TaskSummary; import org.drools.task.service.TaskService.ScheduledTaskDeadline; import javax.persistence.*; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; public class TaskServiceSession { private final TaskService service; private final EntityManager em; private Map<String, RuleBase> ruleBases; private Map<String, Map<String, Object>> globals; private EventKeys eventKeys; public TaskServiceSession(final TaskService service, final EntityManager em) { this.service = service; this.em = em; } public void dispose() { em.close(); } public EntityManager getEntityManager() { return em; } public void setRuleBase(final String type, final RuleBase ruleBase) { if (ruleBases == null) { ruleBases = new HashMap<String, RuleBase>(); } ruleBases.put(type, ruleBase); } public void setGlobals(final String type, final Map<String, Object> globals) { if (this.globals == null) { this.globals = new HashMap<String, Map<String, Object>>(); } this.globals.put(type, globals); } public void addUser(final User user) { persistInTransaction(user); } public void addGroup(final Group group) { persistInTransaction(group); } /** * Runs any custom rules against the specified Task and ContentData to ensure that the * task is allowed to be added. If the task cannot be added, a <code>CannotAddTaskException</code> * will be thrown. * @param task task that is being added * @param contentData content data for task * @throws CannotAddTaskException throw if the task is not allowed to be added */ private void executeTaskAddRules(final Task task, final ContentData contentData) throws CannotAddTaskException { RuleBase ruleBase = ruleBases.get("addTask"); if (ruleBase != null) { StatefulSession session = ruleBase.newStatefulSession(); Map<String, Object> globals = this.globals.get("addTask"); if (globals != null) { for (Map.Entry<String, Object> entry : globals.entrySet()) { session.setGlobal(entry.getKey(), entry.getValue()); } } TaskServiceRequest request = new TaskServiceRequest("addTask", null, null); session.setGlobal("request", request); session.insert(task); session.insert(contentData); session.fireAllRules(); if (!request.isAllowed()) { StringBuilder error = new StringBuilder("Cannot add Task:\n"); if (request.getReasons() != null) { for (String reason : request.getReasons()) { error.append( reason).append('\n'); } } throw new CannotAddTaskException(error.toString()); } } } public void addTask(final Task task, final ContentData contentData) throws CannotAddTaskException { final TaskData taskData = task.getTaskData(); // initialize the task data Status currentStatus = taskData.initialize(); if (ruleBases != null) { executeTaskAddRules(task, contentData); } // than assign the TaskData an owner and status based on the task assignments PeopleAssignments assignments = task.getPeopleAssignments(); if (assignments != null) { List<OrganizationalEntity> potentialOwners = assignments.getPotentialOwners(); currentStatus = taskData.assignOwnerAndStatus(potentialOwners); } doOperationInTransaction(new TransactedOperation() { public void doOperation() { em.persist(task); if (contentData != null) { Content content = new Content(contentData.getContent()); em.persist(content); taskData.setDocument(content.getId(), contentData); } } }); // schedule after it's been persisted, otherwise the id's won't be assigned if (task.getDeadlines() != null) { scheduleTask(task); } if (currentStatus == Status.Reserved) { // Task was reserved so owner should get icals SendIcal.getInstance().sendIcalForTask(task, service.getUserinfo()); // trigger event support service.getEventSupport().fireTaskClaimed(task.getId(), task.getTaskData().getActualOwner().getId()); } } private void scheduleTask(final Task task) { final long now = System.currentTimeMillis(); final List<Deadline> startDeadlines = task.getDeadlines().getStartDeadlines(); if (startDeadlines != null) { scheduleDeadlines(startDeadlines, now, task.getId()); } final List<Deadline> endDeadlines = task.getDeadlines().getEndDeadlines(); if (endDeadlines != null) { scheduleDeadlines(endDeadlines, now, task.getId()); } } private void scheduleDeadlines(final List<Deadline> deadlines, final long now, final long taskId) { for (Deadline deadline : deadlines) { if (!deadline.isEscalated()) { // only escalate when true - typically this would only be true // if the user is requested that the notification should never be escalated Date date = deadline.getDate(); service.schedule(new ScheduledTaskDeadline(taskId, deadline.getId(), service), date.getTime() - now); } } } void evalCommand(final Operation operation, final List<OperationCommand> commands, final Task task, final User user, final OrganizationalEntity targetEntity, List<String> groupIds) throws PermissionDeniedException { final TaskData taskData = task.getTaskData(); boolean statusMatched = false; for (OperationCommand command : commands) { // first find out if we have a matching status if (command.getStatus() != null) { for (Status status : command.getStatus()) { if (taskData.getStatus() == status) { statusMatched = true; // next find out if the user can execute this doOperation if (!isAllowed(command, task, user, groupIds)) { String errorMessage = "User '" + user + "' does not have permissions to execution operation '" + operation + "' on task id " + task.getId(); throw new PermissionDeniedException(errorMessage); } commands(command, task, user, targetEntity); } } } if (command.getPreviousStatus() != null) { for (Status status : command.getPreviousStatus()) { if (taskData.getPreviousStatus() == status) { statusMatched = true; // next find out if the user can execute this doOperation if (!isAllowed(command, task, user, groupIds)) { String errorMessage = "User '" + user + "' does not have permissions to execution operation '" + operation + "' on task id " + task.getId(); throw new PermissionDeniedException(errorMessage); } commands(command, task, user, targetEntity); } } } } if (!statusMatched) { String errorMessage = "User '" + user + "' was unable to execution operation '" + operation + "' on task id " + task.getId() + " due to no 'current status' matchines"; throw new PermissionDeniedException(errorMessage); } } private static boolean isAllowed(final OperationCommand command, final Task task, final User user, final List<String> groupIds) { final PeopleAssignments people = task.getPeopleAssignments(); final TaskData taskData = task.getTaskData(); boolean operationAllowed = false; for (Allowed allowed : command.getAllowed()) { if (operationAllowed) { break; } switch (allowed) { case Owner: { operationAllowed = (taskData.getActualOwner() != null && taskData.getActualOwner().equals(user)); break; } case Initiator: { operationAllowed = (taskData.getCreatedBy() != null && (taskData.getCreatedBy().equals(user)) || (groupIds != null && groupIds.contains(taskData.getCreatedBy().getId()))); break; } case PotentialOwner: { operationAllowed = isAllowed(user, groupIds, people.getPotentialOwners()); break; } case BusinessAdministrator: { operationAllowed = isAllowed(user, groupIds, people.getBusinessAdministrators()); break; } } } if (operationAllowed && command.isUserIsExplicitPotentialOwner()) { // if user has rights to execute the command, make sure user is explicitely specified (not as a group) operationAllowed = people.getPotentialOwners().contains(user); } if (operationAllowed && command.isSkippable()) { operationAllowed = taskData.isSkipable(); } return operationAllowed; } private void commands(final OperationCommand command, final Task task, final User user, final OrganizationalEntity targetEntity) { final PeopleAssignments people = task.getPeopleAssignments(); final TaskData taskData = task.getTaskData(); if (command.getNewStatus() != null) { taskData.setStatus(command.getNewStatus()); } else if (command.isSetToPreviousStatus()) { taskData.setStatus(taskData.getPreviousStatus()); } if (command.isAddTargetEntityToPotentialOwners() && !people.getPotentialOwners().contains(targetEntity)) { people.getPotentialOwners().add(targetEntity); } if (command.isRemoveUserFromPotentialOwners()) { people.getPotentialOwners().remove(user); } if (command.isSetNewOwnerToUser()) { taskData.setActualOwner(user); } if (command.isSetNewOwnerToNull()) { taskData.setActualOwner(null); } if (command.getExec() != null) { switch (command.getExec()) { case Claim: { taskData.setActualOwner((User) targetEntity); // Task was reserved so owner should get icals SendIcal.getInstance().sendIcalForTask(task, service.getUserinfo()); // trigger event support service.getEventSupport().fireTaskClaimed(task.getId(), task.getTaskData().getActualOwner().getId()); break; } } } } public void taskOperation(final Operation operation, final long taskId, final String userId, final String targetEntityId, final ContentData data, List<String> groupIds) throws TaskException { OrganizationalEntity targetEntity = null; if (targetEntityId != null) { targetEntity = getEntity(OrganizationalEntity.class, targetEntityId); } final Task task = getTask(taskId); final User user = getEntity(User.class, userId); try { final List<OperationCommand> commands = service.getCommandsForOperation(operation); beginOrUseExistingTransaction(); evalCommand(operation, commands, task, user, targetEntity, groupIds); switch (operation) { case Claim: { taskClaimOperation(task); break; } case Complete: { taskCompleteOperation(task, data); break; } case Fail: { taskFailOperation(task, data); break; } case Skip: { taskSkipOperation(task, userId); break; } } } catch (RuntimeException e) { em.getTransaction().rollback(); doOperationInTransaction(new TransactedOperation() { public void doOperation() { task.getTaskData().setStatus(Status.Error); } }); throw e; } finally { if (em.getTransaction().isActive()) { em.getTransaction().commit(); } } } private void taskClaimOperation(final Task task) { // Task was reserved so owner should get icals SendIcal.getInstance().sendIcalForTask(task, service.getUserinfo()); // trigger event support service.getEventSupport().fireTaskClaimed(task.getId(), task.getTaskData().getActualOwner().getId()); } private void taskCompleteOperation(final Task task, final ContentData data) { if (data != null) { doOperationInTransaction(new TransactedOperation() { public void doOperation() { final Content content = new Content(); content.setContent(data.getContent()); em.persist(content); final TaskData taskData = task.getTaskData(); taskData.setOutput(content.getId(), data); } }); } // trigger event support service.getEventSupport().fireTaskCompleted(task.getId(), task.getTaskData().getActualOwner().getId()); checkSubTaskStrategy(task); } private void taskFailOperation(final Task task, final ContentData data) { // set fault data if (data != null) { doOperationInTransaction(new TransactedOperation() { public void doOperation() { final Content content = new Content(data.getContent()); em.persist(content); final TaskData taskData = task.getTaskData(); taskData.setFault(content.getId(), (FaultData) data); } }); } // trigger event support service.getEventSupport().fireTaskFailed(task.getId(), task.getTaskData().getActualOwner().getId()); } private void taskSkipOperation(final Task task, final String userId) { // trigger event support service.getEventSupport().fireTaskSkipped(task.getId(), userId); checkSubTaskStrategy(task); } public Task getTask(final long taskId) { return getEntity(Task.class, taskId); } public void addComment(final long taskId, final Comment comment) { final Task task = getTask(taskId); doOperationInTransaction(new TransactedOperation() { public void doOperation() { task.getTaskData().addComment(comment); } }); } public void addAttachment(final long taskId, final Attachment attachment, final Content content) { final Task task = getTask(taskId); doOperationInTransaction(new TransactedOperation() { public void doOperation() { em.persist(content); attachment.setContent(content); task.getTaskData().addAttachment(attachment); } }); } public void setDocumentContent(final long taskId, final Content content) { final Task task = getTask(taskId); doOperationInTransaction(new TransactedOperation() { public void doOperation() { em.persist(content); task.getTaskData().setDocumentContentId(content.getId()); } }); } public Content getContent(final long contentId) { return getEntity(Content.class, contentId); } public void deleteAttachment(final long taskId, final long attachmentId, final long contentId) { // @TODO I can't get this to work with HQL deleting the Attachment. Hibernate needs both the item removed from the collection // and also the item deleted, so for now have to load the entire Task, I suspect that this is due to using the same EM which // is caching things. final Task task = getTask(taskId); doOperationInTransaction(new TransactedOperation() { public void doOperation() { final Attachment removedAttachment = task.getTaskData().removeAttachment(attachmentId); if (removedAttachment != null) { // need to do this otherwise it just removes the link id, without removing the attachment em.remove(removedAttachment); } // we do this as HQL to avoid streaming in the entire HQL final String deleteContent = "delete from Content c where c.id = :id"; em.createQuery(deleteContent).setParameter("id", contentId).executeUpdate(); } }); } public void deleteComment(final long taskId, final long commentId) { // @TODO I can't get this to work with HQL deleting the Comment. Hibernate needs both the item removed from the collection // and also the item deleted, so for now have to load the entire Task, I suspect that this is due to using the same EM which // is caching things. final Task task = getTask(taskId); doOperationInTransaction(new TransactedOperation() { public void doOperation() { final Comment removedComment = task.getTaskData().removeComment(commentId); if (removedComment != null) { // need to do this otherwise it just removes the link id, without removing the attachment em.remove(removedComment); } } }); } public List<DeadlineSummary> getUnescalatedDeadlines() { return (List<DeadlineSummary>) em.createNamedQuery("UnescalatedDeadlines").getResultList(); } public Task getTaskByWorkItemId(final long workItemId) { final Query task = em.createNamedQuery("TaskByWorkItemId"); task.setParameter("workItemId", workItemId); return (Task) task.getSingleResult(); } public List<TaskSummary> getTasksOwned(final String userId, final String language) { final Query tasksOwned = em.createNamedQuery("TasksOwned"); tasksOwned.setParameter("userId", userId); tasksOwned.setParameter("language", language); return (List<TaskSummary>) tasksOwned.getResultList(); } public List<TaskSummary> getTasksAssignedAsBusinessAdministrator(final String userId, final String language) { final Query tasksAssignedAsBusinessAdministrator = em.createNamedQuery("TasksAssignedAsBusinessAdministrator"); tasksAssignedAsBusinessAdministrator.setParameter("userId", userId); tasksAssignedAsBusinessAdministrator.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsBusinessAdministrator.getResultList(); } public List<TaskSummary> getTasksAssignedAsExcludedOwner(final String userId, final String language) { final Query tasksAssignedAsExcludedOwner = em.createNamedQuery("TasksAssignedAsExcludedOwner"); tasksAssignedAsExcludedOwner.setParameter("userId", userId); tasksAssignedAsExcludedOwner.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsExcludedOwner.getResultList(); } public List<TaskSummary> getTasksAssignedAsPotentialOwner(final String userId, final String language) { final Query tasksAssignedAsPotentialOwner = em.createNamedQuery("TasksAssignedAsPotentialOwner"); tasksAssignedAsPotentialOwner.setParameter("userId", userId); tasksAssignedAsPotentialOwner.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsPotentialOwner.getResultList(); } public List<TaskSummary> getTasksAssignedAsPotentialOwner(final String userId, final List<String> groupIds, final String language) { final Query tasksAssignedAsPotentialOwner = em.createNamedQuery("TasksAssignedAsPotentialOwnerWithGroups"); tasksAssignedAsPotentialOwner.setParameter("userId", userId); tasksAssignedAsPotentialOwner.setParameter("groupIds", groupIds); tasksAssignedAsPotentialOwner.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsPotentialOwner.getResultList(); } public List<TaskSummary> getSubTasksAssignedAsPotentialOwner(final long parentId, final String userId, final String language) { final Query tasksAssignedAsPotentialOwner = em.createNamedQuery("SubTasksAssignedAsPotentialOwner"); tasksAssignedAsPotentialOwner.setParameter("parentId", parentId); tasksAssignedAsPotentialOwner.setParameter("userId", userId); tasksAssignedAsPotentialOwner.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsPotentialOwner.getResultList(); } public List<TaskSummary> getTasksAssignedAsPotentialOwnerByGroup(final String groupId, final String language) { final Query tasksAssignedAsPotentialOwnerByGroup = em.createNamedQuery("TasksAssignedAsPotentialOwnerByGroup"); tasksAssignedAsPotentialOwnerByGroup.setParameter("groupId", groupId); tasksAssignedAsPotentialOwnerByGroup.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsPotentialOwnerByGroup.getResultList(); } public List<TaskSummary> getSubTasksByParent(final long parentId, final String language) { final Query subTaskByParent = em.createNamedQuery("GetSubTasksByParentTaskId"); subTaskByParent.setParameter("parentId", parentId); subTaskByParent.setParameter("language", language); return (List<TaskSummary>) subTaskByParent.getResultList(); } public List<TaskSummary> getTasksAssignedAsRecipient(final String userId, final String language) { final Query tasksAssignedAsRecipient = em.createNamedQuery("TasksAssignedAsRecipient"); tasksAssignedAsRecipient.setParameter("userId", userId); tasksAssignedAsRecipient.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsRecipient.getResultList(); } public List<TaskSummary> getTasksAssignedAsTaskInitiator(final String userId, final String language) { final Query tasksAssignedAsTaskInitiator = em.createNamedQuery("TasksAssignedAsTaskInitiator"); tasksAssignedAsTaskInitiator.setParameter("userId", userId); tasksAssignedAsTaskInitiator.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsTaskInitiator.getResultList(); } public List<TaskSummary> getTasksAssignedAsTaskStakeholder(final String userId, final String language) { final Query tasksAssignedAsTaskStakeholder = em.createNamedQuery("TasksAssignedAsTaskStakeholder"); tasksAssignedAsTaskStakeholder.setParameter("userId", userId); tasksAssignedAsTaskStakeholder.setParameter("language", language); return (List<TaskSummary>) tasksAssignedAsTaskStakeholder.getResultList(); } public static boolean isAllowed(final User user, final List<OrganizationalEntity>[] people) { for (List<OrganizationalEntity> list : people) { if (isAllowed(user, null, list)) { return true; } } return false; } static boolean isAllowed(final User user, final List<String> groupIds, final List<OrganizationalEntity> entities) { // for now just do a contains, I'll figure out group membership later. for (OrganizationalEntity entity : entities) { if (entity instanceof User && entity.equals(user)) { return true; } if (entity instanceof Group && groupIds != null && groupIds.contains(entity.getId())) { return true; } } return false; } private void checkSubTaskStrategy(final Task task) { for (SubTasksStrategy strategy : task.getSubTaskStrategies()) { strategy.execute(this, service, task); } final Task parentTask; if (task.getTaskData().getParentId() != -1) { parentTask = getTask(task.getTaskData().getParentId()); for (SubTasksStrategy strategy : parentTask.getSubTaskStrategies()) { strategy.execute(this, service, parentTask); } } } /** * Returns the entity of the specified class by for the specified primaryKey. * * @param entityClass - class of entity to return * @param primaryKey - key of entity * @return entity or <code>EntityNotFoundException</code> if the entity cannot be found * @throws EntityNotFoundException if entity not found */ private <T> T getEntity(final Class<T> entityClass, final Object primaryKey) { final T entity = em.find(entityClass, primaryKey); if (entity == null) { throw new EntityNotFoundException("No " + entityClass.getSimpleName() + " with ID " + primaryKey + " was found!"); } return entity; } /** * Persists the specified object within a new transaction. If there are any problems, the * transaction will be rolled back. * * @param object object to persists */ private void persistInTransaction(final Object object) { doOperationInTransaction(new TransactedOperation() { public void doOperation() { em.persist(object); } }); } /** * Starts a transaction if there isn't one currently in progess. */ private void beginOrUseExistingTransaction() { final EntityTransaction tx = em.getTransaction(); if (!tx.isActive()) { tx.begin(); } } /** * Executes the specified operation within a transaction. Note that if there is a currently active * transaction, if will reuse it. * * @param operation operation to execute */ private void doOperationInTransaction(final TransactedOperation operation) { final EntityTransaction tx = em.getTransaction(); try { if (!tx.isActive()) { tx.begin(); } operation.doOperation(); tx.commit(); } finally { if (tx.isActive()) { tx.rollback(); } } } private interface TransactedOperation { void doOperation(); } }