/**
 * 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.jbpm.task.service;

import org.drools.RuleBase;
import org.drools.StatefulSession;
import org.jbpm.eventmessaging.EventKeys;
import org.jbpm.task.*;
import org.jbpm.task.query.DeadlineSummary;
import org.jbpm.task.query.TaskSummary;
import org.jbpm.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;
                }
                case Anyone: {
                	operationAllowed = true;
                	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;
                }
                case Remove: {
                	taskRemoveOperation(task, user);
                	break;
                }
                case Register: {
                	taskRegisterOperation(task, user);
                	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) {
        	setOutput(task.getId(), task.getTaskData().getActualOwner().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) {
        	setFault(task.getId(), task.getTaskData().getActualOwner().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 List<?> query(final String qlString, final Integer size, final Integer offset) {
    	final Query genericQuery = em.createQuery(qlString);
    	genericQuery.setMaxResults(size);
    	genericQuery.setFirstResult(offset);
    	return genericQuery.getResultList();
    }
    
    private void taskRemoveOperation(final Task task, final User user) {
		if (task.getPeopleAssignments().getRecipients().contains(user)) {
			task.getPeopleAssignments().getRecipients().remove(user);
		} else {
			throw new RuntimeException("Couldn't remove user " + user.getId() + " since it isn't a notification recipient");
		}
    }
    
    private void taskRegisterOperation(final Task task, final User user) {
		if (!task.getPeopleAssignments().getRecipients().contains(user)) {
			task.getPeopleAssignments().getRecipients().add(user);
		}
    }
    
    public void nominateTask(final long taskId, String userId, final List<OrganizationalEntity> potentialOwners) {
    	final Task task = getEntity(Task.class, taskId);
    	final User user = getEntity(User.class, userId);
    	if (isAllowed(user, null, task.getPeopleAssignments().getBusinessAdministrators())) {
	    	doOperationInTransaction(new TransactedOperation() {
				public void doOperation() {
					task.getTaskData().assignOwnerAndStatus(potentialOwners);
					if (task.getTaskData().getStatus() == Status.Ready) {
						task.getPeopleAssignments().setPotentialOwners(potentialOwners);
					}
				}
	    	});
    	} else {
    		throw new PermissionDeniedException("User " + userId + " is not allowed to perform Nominate on Task " + taskId);
    	}
    }
    
    public void setOutput(final long taskId, final String userId, final ContentData outputContentData) {
    	doOperationInTransaction(new TransactedOperation() {
    		public void doOperation() {
    			Task task = getEntity(Task.class, taskId);
    			if (!userId.equals(task.getTaskData().getActualOwner().getId())) {
    				throw new RuntimeException("User " + userId + " is not the actual owner of the task " + taskId + " and can't perform setOutput");
    			}
    			Content content = new Content();
    			content.setContent(outputContentData.getContent());
    			em.persist(content);
    			task.getTaskData().setOutput(content.getId(), outputContentData);
    		}
    	});
    }
    
    public void setFault(final long taskId, final String userId, final FaultData faultContentData) {
    	doOperationInTransaction(new TransactedOperation() {
    		public void doOperation() {
    			Task task = getEntity(Task.class, taskId);
    			if (!userId.equals(task.getTaskData().getActualOwner().getId())) {
    				throw new RuntimeException("User " + userId + " is not the actual owner of the task " + taskId + " and can't perform setFault");
    			}
    			Content content = new Content();
    			content.setContent(faultContentData.getContent());
    			em.persist(content);
    			task.getTaskData().setFault(content.getId(), faultContentData);
    		}
    	});
    }
    
    public void setPriority(final long taskId, final String userId, final int priority) {
    	doOperationInTransaction(new TransactedOperation() {
    		public void doOperation() {
    			Task task = getEntity(Task.class, taskId);
    			task.setPriority(priority);
    		}
    	});
    }
    
    public void deleteOutput(final long taskId, final String userId) {
    	doOperationInTransaction(new TransactedOperation() {
    		public void doOperation() {
    			Task task = getEntity(Task.class, taskId);
    			if (!userId.equals(task.getTaskData().getActualOwner().getId())) {
    				throw new RuntimeException("User " + userId + " is not the actual owner of the task " + taskId + " and can't perform deleteOutput");
    			}
    			long contentId = task.getTaskData().getOutputContentId();
    			Content content = getEntity(Content.class, contentId);
    			ContentData data = new ContentData();
    			em.remove(content);
    			task.getTaskData().setOutput(0, data);
    		}
    	});
    }
    
    public void deleteFault(final long taskId, final String userId) {
    	doOperationInTransaction(new TransactedOperation() {
    		public void doOperation() {
    			Task task = getEntity(Task.class, taskId);
    			if (!userId.equals(task.getTaskData().getActualOwner().getId())) {
    				throw new RuntimeException("User " + userId + " is not the actual owner of the task " + taskId + " and can't perform deleteFault");
    			}
    			long contentId = task.getTaskData().getFaultContentId();
    			Content content = getEntity(Content.class, contentId);
    			FaultData data = new FaultData();
    			em.remove(content);
    			task.getTaskData().setFault(0, data);
    		}
    	});
    }
    
    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();
    }
}
