org.libreplan.web.limitingresources.LimitingResourceQueueModel.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.web.limitingresources.LimitingResourceQueueModel.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico de Galicia
 * Copyright (C) 2010-2011 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.libreplan.web.limitingresources;

import static org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement.isAfter;
import static org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement.isInTheMiddle;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;

import org.apache.commons.lang3.Validate;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.jgrapht.DirectedGraph;
import org.jgrapht.traverse.TopologicalOrderIterator;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.CalendarAvailability;
import org.libreplan.business.calendars.entities.CalendarData;
import org.libreplan.business.calendars.entities.CalendarException;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.daos.IDependencyDAO;
import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.limiting.daos.ILimitingResourceQueueDAO;
import org.libreplan.business.planner.limiting.daos.ILimitingResourceQueueDependencyDAO;
import org.libreplan.business.planner.limiting.daos.ILimitingResourceQueueElementDAO;
import org.libreplan.business.planner.limiting.entities.AllocationSpec;
import org.libreplan.business.planner.limiting.entities.DateAndHour;
import org.libreplan.business.planner.limiting.entities.Gap;
import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueue;
import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueueWithQueueElement;
import org.libreplan.business.planner.limiting.entities.InsertionRequirements;
import org.libreplan.business.planner.limiting.entities.LimitingResourceAllocator;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueDependency;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueDependency.QueueDependencyType;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionSatisfaction;
import org.libreplan.business.resources.entities.LimitingResourceQueue;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.scenarios.bootstrap.PredefinedScenarios;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.users.daos.IOrderAuthorizationDAO;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.business.users.entities.OrderAuthorization;
import org.libreplan.business.users.entities.OrderAuthorizationType;
import org.libreplan.business.users.entities.User;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.web.common.concurrentdetection.OnConcurrentModification;
import org.libreplan.web.limitingresources.QueuesState.Edge;
import org.libreplan.web.planner.order.SaveCommandBuilder;
import org.libreplan.web.security.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.zkoss.ganttz.timetracker.zoom.ZoomLevel;
import org.zkoss.ganttz.util.Interval;

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@OnConcurrentModification(goToPage = "/planner/index.zul;limiting_resources")
public class LimitingResourceQueueModel implements ILimitingResourceQueueModel {

    @Autowired
    private IOrderDAO orderDAO;

    @Autowired
    private IUserDAO userDAO;

    @Autowired
    private IOrderAuthorizationDAO orderAuthorizationDAO;

    @Autowired
    private ILimitingResourceQueueElementDAO limitingResourceQueueElementDAO;

    @Autowired
    private ILimitingResourceQueueDAO limitingResourceQueueDAO;

    @Autowired
    private ITaskElementDAO taskDAO;

    @Autowired
    private ILimitingResourceQueueDependencyDAO limitingResourceQueueDependencyDAO;

    @Autowired
    private IDependencyDAO dependencyDAO;

    private QueuesState queuesState;

    private Interval viewInterval;

    private LimitingResourceQueueElement beingEdited;

    private Set<LimitingResourceQueueElement> toBeRemoved = new HashSet<>();

    private Set<LimitingResourceQueueElement> toBeSaved = new HashSet<>();

    private Set<TaskElement> parentElementsToBeUpdated = new HashSet<>();

    private Scenario master;

    private Map<LimitingResourceQueueElement, HashSet<LimitingResourceQueueDependency>> toBeSavedDependencies = new HashMap<>();

    private boolean checkAllocationIsAppropriative = true;

    @Override
    @Transactional(readOnly = true)
    public void initGlobalView() {
        doGlobalView();
    }

    private void doGlobalView() {
        master = PredefinedScenarios.MASTER.getScenario();
        List<LimitingResourceQueueElement> unassigned = findUnassignedLimitingResourceQueueElements();
        List<LimitingResourceQueue> queues = loadLimitingResourceQueues();
        queuesState = new QueuesState(queues, unassigned);
        final Date startingDate = getEarliestDate();
        Date endDate = (new LocalDate(startingDate)).plusYears(2).toDateTimeAtCurrentTime().toDate();
        viewInterval = new Interval(startingDate, endDate);

        Date currentDate = new Date();
        viewInterval = new Interval(startingDate.after(currentDate) ? currentDate : startingDate, endDate);

    }

    private Date getEarliestDate() {
        final LimitingResourceQueueElement element = getEarliestQueueElement();
        return (element != null) ? element.getStartDate().toDateTimeAtCurrentTime().toDate() : new Date();
    }

    private LimitingResourceQueueElement getEarliestQueueElement() {
        LimitingResourceQueueElement earliestQueueElement = null;

        for (LimitingResourceQueue each : queuesState.getQueues()) {
            LimitingResourceQueueElement element = getFirstLimitingResourceQueueElement(each);
            if (element == null) {
                continue;
            }

            if (earliestQueueElement == null || isEarlier(element, earliestQueueElement)) {
                earliestQueueElement = element;
            }
        }

        return earliestQueueElement;
    }

    private boolean isEarlier(LimitingResourceQueueElement arg1, LimitingResourceQueueElement arg2) {
        return arg1.getStartDate().isBefore(arg2.getStartDate());
    }

    private LimitingResourceQueueElement getFirstLimitingResourceQueueElement(LimitingResourceQueue queue) {
        return getFirstChild(queue.getLimitingResourceQueueElements());
    }

    private LimitingResourceQueueElement getFirstChild(SortedSet<LimitingResourceQueueElement> elements) {
        return elements.isEmpty() ? null : elements.iterator().next();
    }

    /**
     * Loads unassigned {@link LimitingResourceQueueElement} from DB.
     *
     * @return {@link List<LimitingResourceQueueElement>}
     */
    private List<LimitingResourceQueueElement> findUnassignedLimitingResourceQueueElements() {
        return initializeLimitingResourceQueueElements(limitingResourceQueueElementDAO.getUnassigned());
    }

    private List<LimitingResourceQueueElement> initializeLimitingResourceQueueElements(
            List<LimitingResourceQueueElement> elements) {

        for (LimitingResourceQueueElement each : elements) {
            initializeLimitingResourceQueueElement(each);
        }

        return elements;
    }

    private void initializeLimitingResourceQueueElement(LimitingResourceQueueElement element) {
        ResourceAllocation<?> resourceAllocation = element.getResourceAllocation();
        resourceAllocation.switchToScenario(master);
        resourceAllocation = initializeResourceAllocationIfNecessary(resourceAllocation);
        element.setResourceAllocation(resourceAllocation);
        initializeTask(resourceAllocation.getTask());
        initializeResourceIfAny(element.getResource());
    }

    private void initializeTask(Task task) {
        if (hasResourceAllocation(task)) {

            ResourceAllocation<?> resourceAllocation = initializeResourceAllocationIfNecessary(
                    getResourceAllocation(task));

            task.setResourceAllocation(resourceAllocation);
        }

        Hibernate.initialize(task);

        for (ResourceAllocation<?> each : task.getAllResourceAllocations()) {
            Hibernate.initialize(each);
        }

        initializeDependencies(task);
        initializeTaskSource(task.getTaskSource());
        initializeRootOrder(task);
    }

    private void initializeDependencies(Task task) {
        for (Dependency each : task.getDependenciesWithThisOrigin()) {
            Hibernate.initialize(each.getDestination());
        }

        for (Dependency each : task.getDependenciesWithThisDestination()) {
            Hibernate.initialize(each.getOrigin());
        }
    }

    private boolean hasResourceAllocation(Task task) {
        return !task.getLimitingResourceAllocations().isEmpty();
    }

    private ResourceAllocation<?> getResourceAllocation(Task task) {
        return task.getLimitingResourceAllocations().iterator().next();
    }

    private void initializeTaskSource(TaskSource taskSource) {
        Hibernate.initialize(taskSource);
        for (HoursGroup each : taskSource.getHoursGroups()) {
            Hibernate.initialize(each);
        }
    }

    /**
     * FIXME: Needed to fetch order.name in QueueComponent.composeTooltiptext
     * Try to replace it with a HQL query instead of iterating all the way up through order.
     */
    private void initializeRootOrder(Task task) {
        Hibernate.initialize(task.getOrderElement());
        OrderElement order = task.getOrderElement();

        do {
            Hibernate.initialize(order.getParent());
            order = order.getParent();
        } while (order.getParent() != null);
    }

    private void initializeCalendarIfAny(BaseCalendar calendar) {
        if (calendar != null) {
            Hibernate.initialize(calendar);
            initializeCalendarAvailabilities(calendar);
            initializeCalendarExceptions(calendar);
            initializeCalendarDataVersions(calendar);
        }
    }

    private void initializeCalendarAvailabilities(BaseCalendar calendar) {
        for (CalendarAvailability each : calendar.getCalendarAvailabilities()) {
            Hibernate.initialize(each);
        }
    }

    private void initializeCalendarExceptions(BaseCalendar calendar) {
        for (CalendarException each : calendar.getExceptions()) {
            Hibernate.initialize(each);
            Hibernate.initialize(each.getType());
        }
    }

    private void initializeCalendarDataVersions(BaseCalendar calendar) {
        for (CalendarData each : calendar.getCalendarDataVersions()) {
            Hibernate.initialize(each);
            Hibernate.initialize(each.getHoursPerDay());
            initializeCalendarIfAny(each.getParent());
        }
    }

    private ResourceAllocation<?> initializeResourceAllocationIfNecessary(
            ResourceAllocation<?> resourceAllocation) {
        ResourceAllocation<?> newResourceAllocation = resourceAllocation;

        if (newResourceAllocation instanceof HibernateProxy) {

            newResourceAllocation = (ResourceAllocation<?>) ((HibernateProxy) newResourceAllocation)
                    .getHibernateLazyInitializer().getImplementation();

            if (newResourceAllocation instanceof GenericResourceAllocation) {
                GenericResourceAllocation generic = (GenericResourceAllocation) newResourceAllocation;
                initializeCriteria(generic.getCriterions());
            }

            Hibernate.initialize(newResourceAllocation.getAssignments());
            Hibernate.initialize(newResourceAllocation.getLimitingResourceQueueElement());
        }

        return newResourceAllocation;
    }

    private void initializeCriteria(Set<Criterion> criteria) {
        for (Criterion each : criteria) {
            initializeCriterion(each);
        }
    }

    private void initializeCriterion(Criterion criterion) {
        Hibernate.initialize(criterion);
        Hibernate.initialize(criterion.getType());
    }

    private List<LimitingResourceQueue> loadLimitingResourceQueues() {
        return initializeLimitingResourceQueues(limitingResourceQueueDAO.getAll());
    }

    private List<LimitingResourceQueue> initializeLimitingResourceQueues(List<LimitingResourceQueue> queues) {
        for (LimitingResourceQueue each : queues) {
            initializeLimitingResourceQueue(each);
        }
        return queues;
    }

    private void initializeLimitingResourceQueue(LimitingResourceQueue queue) {
        initializeResourceIfAny(queue.getResource());
        for (LimitingResourceQueueElement each : queue.getLimitingResourceQueueElements()) {
            initializeLimitingResourceQueueElement(each);
        }
    }

    private void initializeResourceIfAny(Resource resource) {
        if (resource != null) {
            Hibernate.initialize(resource);
            initializeCalendarIfAny(resource.getCalendar());
            resource.getAssignments();

            for (CriterionSatisfaction each : resource.getCriterionSatisfactions()) {
                Hibernate.initialize(each);
                initializeCriterion(each.getCriterion());
            }
        }
    }

    @Override
    @Transactional(readOnly = true)
    public Order getOrderByTask(TaskElement task) {
        return orderDAO.loadOrderAvoidingProxyFor(task.getOrderElement());
    }

    @Override
    public Interval getViewInterval() {
        return viewInterval;
    }

    @Override
    @Transactional(readOnly = true)
    public boolean userCanRead(Order order, String loginName) {
        if (SecurityUtils.isSuperuserOrUserInRoles(UserRole.ROLE_READ_ALL_PROJECTS,
                UserRole.ROLE_EDIT_ALL_PROJECTS)) {
            return true;
        }

        try {
            User user = userDAO.findByLoginName(loginName);
            for (OrderAuthorization authorization : orderAuthorizationDAO.listByOrderUserAndItsProfiles(order,
                    user)) {

                if (authorization.getAuthorizationType() == OrderAuthorizationType.READ_AUTHORIZATION
                        || authorization.getAuthorizationType() == OrderAuthorizationType.WRITE_AUTHORIZATION) {
                    return true;
                }
            }
        } catch (InstanceNotFoundException e) {
            // This case shouldn't happen, because it would mean that there isn't a logged user
            // anyway, if it happened we don't allow the user to pass.
        }
        return false;
    }

    @Override
    public List<LimitingResourceQueue> getLimitingResourceQueues() {
        return queuesState.getQueuesOrderedByResourceName();
    }

    @Override
    public List<LimitingResourceQueueElement> getUnassignedLimitingResourceQueueElements() {
        return queuesState.getUnassigned();
    }

    @Override
    public ZoomLevel calculateInitialZoomLevel() {
        Interval interval = getViewInterval();
        return ZoomLevel.getDefaultZoomByDates(new LocalDate(interval.getStart()),
                new LocalDate(interval.getFinish()));
    }

    @Override
    public List<LimitingResourceQueueElement> assignLimitingResourceQueueElement(
            LimitingResourceQueueElement externalQueueElement) {

        InsertionRequirements requirements = queuesState.getRequirementsFor(externalQueueElement);
        AllocationSpec allocation = insertAtGap(requirements);

        if (allocation == null) {
            return Collections.emptyList();
        }

        applyAllocation(allocation);

        assert allocation.isValid();
        List<LimitingResourceQueueElement> result = new ArrayList<>();
        result.add(requirements.getElement());

        List<LimitingResourceQueueElement> moved = shift(
                queuesState.getPotentiallyAffectedByInsertion(externalQueueElement), requirements.getElement(),
                allocation);

        result.addAll(rescheduleAffectedElementsToSatisfyDependencies(allocation, moved));

        return result;
    }

    /**
     * After an allocation dependencies might be broken, this method unscheduled
     * elements affected by an allocation and reschedule them again in topological order, so dependencies are satisfied.
     *
     * If the allocation was appropriative it also allocates those elements that
     * might be unscheduled before due to the appropriative allocation.
     *
     * @param allocation
     * @param moved
     * @return {@link Collection<? extends LimitingResourceQueueElement>}
     */
    private Collection<? extends LimitingResourceQueueElement> rescheduleAffectedElementsToSatisfyDependencies(
            AllocationSpec allocation, List<LimitingResourceQueueElement> moved) {

        List<LimitingResourceQueueElement> result = new ArrayList<>();
        List<LimitingResourceQueueElement> toReschedule = new ArrayList<>();

        checkAllocationIsAppropriative(false);
        for (LimitingResourceQueueElement each : moved) {
            toReschedule.add(unschedule(each));
        }
        if (allocation.isAppropriative()) {
            toReschedule.addAll(allocation.getUnscheduledElements());
        }

        for (LimitingResourceQueueElement each : queuesState.inTopologicalOrder(toReschedule)) {
            result.addAll(assignLimitingResourceQueueElement(each));
        }

        checkAllocationIsAppropriative(true);

        return result;
    }

    /**
     * Moves elements in order to satisfy dependencies.
     *
     * @param potentiallyAffectedByInsertion
     * @param elementInserted
     * @param allocationAlreadyDone
     * @return the elements that have been moved
     */
    private List<LimitingResourceQueueElement> shift(
            DirectedGraph<LimitingResourceQueueElement, Edge> potentiallyAffectedByInsertion,
            LimitingResourceQueueElement elementInserted, AllocationSpec allocationAlreadyDone) {

        List<AllocationSpec> allocationsToBeDone = getAllocationsToBeDone(potentiallyAffectedByInsertion,
                elementInserted, allocationAlreadyDone);

        List<LimitingResourceQueueElement> result = new ArrayList<>();
        for (AllocationSpec each : allocationsToBeDone) {
            applyAllocation(each);

            LimitingResourceQueueElement element = each.getElement();
            result.add(element);
        }

        return result;
    }

    private List<AllocationSpec> getAllocationsToBeDone(
            DirectedGraph<LimitingResourceQueueElement, Edge> potentiallyAffectedByInsertion,
            LimitingResourceQueueElement elementInserted, AllocationSpec allocationAlreadyDone) {

        List<AllocationSpec> result = new ArrayList<>();
        Map<LimitingResourceQueueElement, AllocationSpec> allocationsToBeDoneByElement = new HashMap<>();
        allocationsToBeDoneByElement.put(elementInserted, allocationAlreadyDone);

        List<LimitingResourceQueueElement> mightNeedShift = withoutElementInserted(elementInserted,
                QueuesState.topologicalIterator(potentiallyAffectedByInsertion));

        for (LimitingResourceQueueElement each : mightNeedShift) {

            AllocationSpec futureAllocation = getAllocationToBeDoneFor(potentiallyAffectedByInsertion,
                    allocationsToBeDoneByElement, each);

            if (futureAllocation != null) {
                result.add(futureAllocation);
                allocationsToBeDoneByElement.put(each, futureAllocation);
            }
        }

        return result;
    }

    private List<LimitingResourceQueueElement> withoutElementInserted(LimitingResourceQueueElement elementInserted,
            final TopologicalOrderIterator<LimitingResourceQueueElement, Edge> topologicalIterator) {

        List<LimitingResourceQueueElement> result = QueuesState.toList(topologicalIterator);
        result.remove(elementInserted);

        return result;
    }

    private AllocationSpec getAllocationToBeDoneFor(
            DirectedGraph<LimitingResourceQueueElement, Edge> potentiallyAffectedByInsertion,
            Map<LimitingResourceQueueElement, AllocationSpec> allocationsToBeDoneByElement,
            LimitingResourceQueueElement current) {

        Validate.isTrue(!current.isDetached());
        DateAndHour newStart = current.getStartTime();
        DateAndHour newEnd = current.getEndTime();

        Map<LimitingResourceQueueElement, List<Edge>> incoming = bySource(
                potentiallyAffectedByInsertion.incomingEdgesOf(current));

        for (Entry<LimitingResourceQueueElement, List<Edge>> each : incoming.entrySet()) {
            AllocationSpec previous = allocationsToBeDoneByElement.get(each.getKey());
            if (previous != null) {
                newStart = DateAndHour.max(newStart, getStartFrom(previous, each.getValue()));
                newEnd = DateAndHour.max(newEnd, getEndFrom(previous, each.getValue()));
            }
        }

        if (current.getStartTime().compareTo(newStart) == 0 && current.getEndTime().compareTo(newEnd) == 0) {
            return null;
        }

        InsertionRequirements requirements = InsertionRequirements.create(current, newStart, newEnd);
        GapOnQueue gap = Gap.untilEnd(current, newStart).onQueue(current.getLimitingResourceQueue());
        AllocationSpec result = requirements.guessValidity(gap);
        assert result.isValid();

        return result;
    }

    private DateAndHour getStartFrom(AllocationSpec previous, List<Edge> edges) {
        DateAndHour result = null;
        for (Edge each : edges) {
            result = DateAndHour.max(result, calculateStart(previous, each.type));
        }
        return result;
    }

    private DateAndHour calculateStart(AllocationSpec previous, QueueDependencyType type) {
        return !type.modifiesDestinationStart() ? null
                : type.calculateDateTargetFrom(previous.getStartInclusive(), previous.getEndExclusive());
    }

    private DateAndHour getEndFrom(AllocationSpec previous, List<Edge> edges) {
        DateAndHour result = null;
        for (Edge each : edges) {
            result = DateAndHour.max(result, calculateEnd(previous, each.type));
        }

        return result;
    }

    private DateAndHour calculateEnd(AllocationSpec previous, QueueDependencyType type) {
        return !type.modifiesDestinationEnd() ? null
                : type.calculateDateTargetFrom(previous.getStartInclusive(), previous.getEndExclusive());
    }

    private Map<LimitingResourceQueueElement, List<Edge>> bySource(Collection<? extends Edge> incomingEdgesOf) {
        Map<LimitingResourceQueueElement, List<Edge>> result = new HashMap<>();
        for (Edge each : incomingEdgesOf) {
            result.putIfAbsent(each.source, new ArrayList<>());
            result.get(each.source).add(each);
        }

        return result;
    }

    /**
     * @return <code>null</code> if no suitable gap found; the allocation found otherwise
     */
    private AllocationSpec insertAtGap(InsertionRequirements requirements) {
        return doAppropriativeIfNecessary(findAllocationSpecFor(requirements), requirements);
    }

    /**
     * Find valid {@link AllocationSpec} taking into account requirements.
     *
     * @param requirements
     * @return {@link AllocationSpec}
     */
    private AllocationSpec findAllocationSpecFor(InsertionRequirements requirements) {
        return findAllocationSpecFor(queuesState.getPotentiallyValidGapsFor(requirements), requirements);
    }

    private AllocationSpec findAllocationSpecFor(List<GapOnQueue> gapsOnQueue, InsertionRequirements requirements) {
        boolean generic = requirements.getElement().isGeneric();
        for (GapOnQueue each : gapsOnQueue) {

            for (GapOnQueue eachSubGap : getSubGaps(each, requirements.getElement(), generic)) {

                AllocationSpec allocation = requirements.guessValidity(eachSubGap);

                if (allocation.isValid()) {
                    return allocation;
                }
            }
        }

        return null;
    }

    private AllocationSpec doAppropriativeIfNecessary(AllocationSpec allocation,
            InsertionRequirements requirements) {
        if (allocation != null) {
            if (checkAllocationIsAppropriative() && requirements.isAppropiativeAllocation(allocation)) {
                return doAppropriativeAllocation(requirements);
            }
            return allocation;
        }
        return null;
    }

    private AllocationSpec insertAtGap(InsertionRequirements requirements, LimitingResourceQueue queue) {
        return doAppropriativeIfNecessary(findAllocationSpecForInQueue(requirements, queue), requirements);
    }

    private AllocationSpec findAllocationSpecForInQueue(InsertionRequirements requirements,
            LimitingResourceQueue queue) {

        List<GapOnQueue> potentiallyValidGapsFor = new ArrayList<>();

        for (GapOnQueue each : queuesState.getPotentiallyValidGapsFor(requirements)) {
            if (each.getOriginQueue().equals(queue)) {
                potentiallyValidGapsFor.add(each);
            }
        }

        return findAllocationSpecFor(potentiallyValidGapsFor, requirements);
    }

    private AllocationSpec doAppropriativeAllocation(InsertionRequirements requirements) {

        LimitingResourceQueueElement element = requirements.getElement();
        List<LimitingResourceQueue> potentiallyValidQueues = getAssignableQueues(element);
        LimitingResourceQueue queue = earliestQueue(potentiallyValidQueues);

        List<LimitingResourceQueueElement> unscheduled = new ArrayList<>();
        AllocationSpec allocation = unscheduleElementsFor(queue, requirements, unscheduled);
        allocation.setUnscheduledElements(queuesState.inTopologicalOrder(unscheduled));

        return allocation;
    }

    /**
     * Returns queue which last element is at a earliest date.
     *
     * @param potentiallyValidQueues
     * @return {@link LimitingResourceQueue}
     */
    private LimitingResourceQueue earliestQueue(List<LimitingResourceQueue> potentiallyValidQueues) {

        LimitingResourceQueue result = null;
        LocalDate latestDate = null;

        for (LimitingResourceQueue each : potentiallyValidQueues) {
            SortedSet<LimitingResourceQueueElement> elements = each.getLimitingResourceQueueElements();

            if (!elements.isEmpty()) {
                LocalDate date = elements.last().getEndDate();

                if (latestDate == null || date.isAfter(latestDate)) {
                    latestDate = date;
                    result = each;
                }
            }
        }

        if (result == null && !potentiallyValidQueues.isEmpty()) {
            result = potentiallyValidQueues.get(0);
        }

        return result;
    }

    private void checkAllocationIsAppropriative(boolean value) {
        checkAllocationIsAppropriative = value;
    }

    private boolean checkAllocationIsAppropriative() {
        return checkAllocationIsAppropriative;
    }

    private List<GapOnQueue> getSubGaps(GapOnQueue each, LimitingResourceQueueElement element, boolean generic) {
        return generic ? each.splitIntoGapsSatisfyingCriteria(element.getCriteria())
                : Collections.singletonList(each);
    }

    private AllocationSpec applyAllocation(final AllocationSpec allocationStillNotDone) {
        applyAllocation(allocationStillNotDone, new IDayAssignmentBehaviour() {

            @Override
            public void allocateDayAssignments(IntraDayDate start, IntraDayDate end) {
                ResourceAllocation<?> resourceAllocation = getResourceAllocation(allocationStillNotDone);
                Resource resource = getResource(allocationStillNotDone);

                List<DayAssignment> assignments = allocationStillNotDone.getAssignmentsFor(resourceAllocation,
                        resource);

                resourceAllocation.allocateLimitingDayAssignments(assignments, start, end);
            }

            private ResourceAllocation<?> getResourceAllocation(AllocationSpec allocation) {
                return allocation.getElement().getResourceAllocation();
            }

            private Resource getResource(AllocationSpec allocation) {
                return allocation.getQueue().getResource();
            }

        });

        return allocationStillNotDone;
    }

    private void applyAllocation(AllocationSpec allocationStillNotDone,
            IDayAssignmentBehaviour allocationBehaviour) {

        // Do day allocation
        allocationBehaviour.allocateDayAssignments(convert(allocationStillNotDone.getStartInclusive()),
                convert(allocationStillNotDone.getEndExclusive()));

        LimitingResourceQueueElement element = allocationStillNotDone.getElement();
        LimitingResourceQueue queue = allocationStillNotDone.getQueue();

        // Update start and end time of task
        updateStartAndEndTimes(element, allocationStillNotDone.getStartInclusive(),
                allocationStillNotDone.getEndExclusive());

        // Add to queue and mark as modified
        addLimitingResourceQueueElementIfNeeded(queue, element);
        markAsModified(element);
    }

    private DateAndHour getEndsAfterBecauseOfGantt(LimitingResourceQueueElement queueElement) {
        return DateAndHour.from(LocalDate.fromDateFields(queueElement.getEarliestEndDateBecauseOfGantt()));
    }

    private List<LimitingResourceQueueElement> assignLimitingResourceQueueElementToQueueAt(
            final LimitingResourceQueueElement element, final LimitingResourceQueue queue,
            final DateAndHour startAt, final DateAndHour endsAfter) {

        // Check if allocation is possible
        InsertionRequirements requirements = queuesState.getRequirementsFor(element, startAt);
        AllocationSpec allocation = insertAtGap(requirements, queue);

        if (!allocation.isValid()) {
            return Collections.emptyList();
        }

        // Do allocation
        applyAllocation(allocation, (start, end) -> {
            List<DayAssignment> assignments = LimitingResourceAllocator.generateDayAssignments(
                    element.getResourceAllocation(), queue.getResource(), startAt, endsAfter);

            element.getResourceAllocation().allocateLimitingDayAssignments(assignments, start, end);
        });

        assert allocation.isValid();

        // Move other tasks to respect dependency constraints
        List<LimitingResourceQueueElement> result = new ArrayList<>();
        result.add(requirements.getElement());

        List<LimitingResourceQueueElement> moved = shift(queuesState.getPotentiallyAffectedByInsertion(element),
                requirements.getElement(), allocation);

        result.addAll(rescheduleAffectedElementsToSatisfyDependencies(allocation, moved));

        return result;
    }

    /**
     * Describes how day assignments are going to be generated for an allocation.
     *
     * @author Diego Pino Garca<dpino@igalia.com>
     */
    private interface IDayAssignmentBehaviour {

        void allocateDayAssignments(IntraDayDate start, IntraDayDate end);

    }

    private void markAsModified(LimitingResourceQueueElement element) {
        if (!toBeSaved.contains(element)) {
            toBeSaved.add(element);
        }
    }

    public Gap createGap(Resource resource, DateAndHour startTime, DateAndHour endTime) {
        return Gap.create(resource, startTime, endTime);
    }

    private void updateStartAndEndTimes(LimitingResourceQueueElement element, DateAndHour startTime,
            DateAndHour endTime) {

        element.setStartDate(startTime.getDate());
        element.setStartHour(startTime.getHour());
        element.setEndDate(endTime.getDate());
        element.setEndHour(endTime.getHour());

        // Update starting and ending dates for associated Task
        Task task = element.getResourceAllocation().getTask();
        updateStartingAndEndingDate(task, convert(startTime), convert(endTime));
    }

    private IntraDayDate convert(DateAndHour dateAndHour) {
        return IntraDayDate.create(dateAndHour.getDate(), EffortDuration.hours(dateAndHour.getHour()));
    }

    private void updateStartingAndEndingDate(Task task, IntraDayDate startDate, IntraDayDate endDate) {
        task.setIntraDayStartDate(startDate);
        task.setIntraDayEndDate(endDate);
        task.explicityMoved(startDate, endDate);
    }

    private void addLimitingResourceQueueElementIfNeeded(LimitingResourceQueue queue,
            LimitingResourceQueueElement element) {
        if (element.getLimitingResourceQueue() == null) {
            queuesState.assignedToQueue(element, queue);
        }
    }

    @Override
    @Transactional
    public void confirm() {
        applyChanges();
    }

    private void applyChanges() {
        removeQueueElements();
        saveQueueElements();
    }

    private void saveQueueElements() {
        for (LimitingResourceQueueElement each : toBeSaved) {
            if (each != null) {
                saveQueueElement(each);
            }
        }
        updateEndDateForParentTasks();
        SaveCommandBuilder.dontPoseAsTransientAndChildrenObjects(getAllocations(toBeSaved));
        toBeSaved.clear();
        parentElementsToBeUpdated.clear();
    }

    private List<ResourceAllocation<?>> getAllocations(
            Collection<? extends LimitingResourceQueueElement> elements) {
        List<ResourceAllocation<?>> result = new ArrayList<>();

        for (LimitingResourceQueueElement each : elements) {

            if (each.getResourceAllocation() != null) {
                result.add(each.getResourceAllocation());
            }
        }

        return result;
    }

    private void saveQueueElement(LimitingResourceQueueElement element) {
        Long previousId = element.getId();
        limitingResourceQueueElementDAO.save(element);
        limitingResourceQueueDAO.flush();

        if (element.isNewObject()) {
            queuesState.idChangedFor(previousId, element);
        }

        element.dontPoseAsTransientObjectAnymore();
        element.getResourceAllocation().dontPoseAsTransientObjectAnymore();

        for (DayAssignment each : element.getDayAssignments()) {
            each.dontPoseAsTransientObjectAnymore();
        }

        if (toBeSavedDependencies.get(element) != null) {
            saveDependencies(toBeSavedDependencies.get(element));
            toBeSavedDependencies.remove(element);
        }

        taskDAO.save(getAssociatedTask(element));
    }

    private void updateEndDateForParentTasks() {
        for (TaskElement task : parentElementsToBeUpdated) {
            TaskElement parent = task;

            while (parent != null) {
                parent.setIntraDayEndDate(null);
                parent.initializeDatesIfNeeded();
                taskDAO.save(parent);
                parent = parent.getParent();
            }
        }
    }

    private void saveDependencies(HashSet<LimitingResourceQueueDependency> dependencies) {
        for (LimitingResourceQueueDependency each : dependencies) {
            limitingResourceQueueDependencyDAO.save(each);
            each.dontPoseAsTransientObjectAnymore();
        }
    }

    private Task getAssociatedTask(LimitingResourceQueueElement element) {
        return element.getResourceAllocation().getTask();
    }

    private void removeQueueElements() {
        for (LimitingResourceQueueElement each : toBeRemoved) {
            removeQueueElement(each);
        }

        toBeRemoved.clear();
    }

    private void removeQueueElement(LimitingResourceQueueElement element) {
        Task task = getAssociatedTask(element);
        removeQueueDependenciesIfAny(task);
        removeQueueElementById(element.getId());
    }

    private void removeQueueElementById(Long id) {
        try {
            limitingResourceQueueElementDAO.remove(id);
        } catch (InstanceNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("Trying to remove non-existing entity");
        }
    }

    private void removeQueueDependenciesIfAny(Task task) {
        for (Dependency each : task.getDependenciesWithThisOrigin()) {
            removeQueueDependencyIfAny(each);
        }

        for (Dependency each : task.getDependenciesWithThisDestination()) {
            removeQueueDependencyIfAny(each);
        }
    }

    private void removeQueueDependencyIfAny(Dependency dependency) {
        LimitingResourceQueueDependency queueDependency = dependency.getQueueDependency();
        if (queueDependency != null) {
            queueDependency.getHasAsOrigin().remove(queueDependency);
            queueDependency.getHasAsDestiny().remove(queueDependency);
            dependency.setQueueDependency(null);
            dependencyDAO.save(dependency);
            removeQueueDependencyById(queueDependency.getId());
        }
    }

    private void removeQueueDependencyById(Long id) {
        try {
            limitingResourceQueueDependencyDAO.remove(id);
        } catch (InstanceNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("Trying to remove non-existing entity");
        }
    }

    /**
     * Unschedules an element from the list of queue elements.
     * The element is later added to the list of unassigned elements.
     */
    @Override
    public LimitingResourceQueueElement unschedule(LimitingResourceQueueElement queueElement) {
        queuesState.unassingFromQueue(queueElement);
        markAsModified(queueElement);
        return queueElement;
    }

    /**
     * Removes an {@link LimitingResourceQueueElement} from the list of unassigned elements.
     */
    @Override
    public void removeUnassignedLimitingResourceQueueElement(LimitingResourceQueueElement element) {
        LimitingResourceQueueElement queueElement = queuesState.getEquivalent(element);

        queueElement.getResourceAllocation().setLimitingResourceQueueElement(null);
        queueElement.getResourceAllocation().getTask().removeAllResourceAllocations();
        queuesState.removeUnassigned(queueElement);
        markAsRemoved(queueElement);
    }

    private void markAsRemoved(LimitingResourceQueueElement element) {
        if (toBeSaved.contains(element)) {
            toBeSaved.remove(element);
        }

        if (!toBeRemoved.contains(element)) {
            toBeRemoved.add(element);
        }
    }

    @Override
    public List<LimitingResourceQueue> getAssignableQueues(LimitingResourceQueueElement element) {
        return queuesState.getAssignableQueues(element);
    }

    @Override
    public List<LimitingResourceQueueElement> nonAppropriativeAllocation(LimitingResourceQueueElement element,
            LimitingResourceQueue queue, DateAndHour startTime) {

        Validate.notNull(element);
        Validate.notNull(queue);
        Validate.notNull(startTime);

        if (element.getLimitingResourceQueue() != null) {
            unschedule(element);
        }

        return assignLimitingResourceQueueElementToQueueAt(element, queue, startTime,
                getEndsAfterBecauseOfGantt(element));
    }

    @Override
    public void init(LimitingResourceQueueElement element) {
        beingEdited = queuesState.getEquivalent(element);
    }

    @Override
    public LimitingResourceQueueElement getLimitingResourceQueueElement() {
        return beingEdited;
    }

    @Override
    public Set<LimitingResourceQueueElement> appropriativeAllocation(
            LimitingResourceQueueElement limitingResourceQueueElement, LimitingResourceQueue limitingResourceQueue,
            DateAndHour allocationTime) {

        Set<LimitingResourceQueueElement> result = new HashSet<>();

        LimitingResourceQueue queue = queuesState.getEquivalent(limitingResourceQueue);
        LimitingResourceQueueElement element = queuesState.getEquivalent(limitingResourceQueueElement);

        InsertionRequirements requirements = queuesState.getRequirementsFor(element, allocationTime);

        if (element.getLimitingResourceQueue() != null) {
            unschedule(element);
        }

        // Unschedule elements in queue since allocationTime and put them in toSchedule
        List<LimitingResourceQueueElement> toSchedule = new ArrayList<>();
        unscheduleElementsFor(queue, requirements, toSchedule);

        result.addAll(assignLimitingResourceQueueElementToQueueAt(element, queue, allocationTime,
                getEndsAfterBecauseOfGantt(element)));

        for (LimitingResourceQueueElement each : queuesState.inTopologicalOrder(toSchedule)) {
            result.addAll(assignLimitingResourceQueueElement(each));
        }

        return result;
    }

    /**
     * Creates room enough in a queue for fitting requirements.
     *
     * Starts unscheduling elements in queue since requirements.earliestPossibleStart()
     * When there's room enough for allocating requirements, the method stops unscheduling more elements.
     *
     * Returns the list of elements that were unscheduled in the process.
     *
     * @param queue
     * @param requirements
     * @return {@link AllocationSpec}
     */
    private AllocationSpec unscheduleElementsFor(LimitingResourceQueue queue, InsertionRequirements requirements,
            List<LimitingResourceQueueElement> result) {

        DateAndHour allocationTime = requirements.getEarliestPossibleStart();
        List<GapOnQueueWithQueueElement> gapsWithQueueElements = queuesState
                .getGapsWithQueueElementsOnQueueSince(queue, allocationTime);

        return unscheduleElementsFor(gapsWithQueueElements, requirements, result);
    }

    private AllocationSpec unscheduleElementsFor(List<GapOnQueueWithQueueElement> gaps,
            InsertionRequirements requirements, List<LimitingResourceQueueElement> result) {

        if (gaps.isEmpty()) {
            return null;
        }

        GapOnQueueWithQueueElement first = gaps.get(0);
        GapOnQueue gapOnQueue = first.getGapOnQueue();

        if (gapOnQueue != null) {
            AllocationSpec allocation = requirements.guessValidity(gapOnQueue);
            if (allocation.isValid()) {
                return allocation;
            }
        }

        result.add(unschedule(first.getQueueElement()));
        if (gaps.size() > 1) {
            gaps.set(1, GapOnQueueWithQueueElement.coalesce(first, gaps.get(1)));
        }

        gaps.remove(0);

        return unscheduleElementsFor(gaps, requirements, result);
    }

    @SuppressWarnings("unchecked")
    public LimitingResourceQueueElement getFirstElementFrom(LimitingResourceQueue queue,
            DateAndHour allocationTime) {
        final List<LimitingResourceQueueElement> elements = new ArrayList(queue.getLimitingResourceQueueElements());

        // First element
        final LimitingResourceQueueElement first = elements.get(0);

        if (isAfter(first, allocationTime)) {
            return first;
        }

        // Rest of elements
        for (final LimitingResourceQueueElement each : elements) {
            if (isInTheMiddle(each, allocationTime) || isAfter(each, allocationTime)) {
                return each;
            }
        }

        return null;
    }

    @Override
    @Transactional(readOnly = true)
    public List<LimitingResourceQueueElement> replaceLimitingResourceQueueElement(
            LimitingResourceQueueElement oldElement, LimitingResourceQueueElement newElement) {

        List<LimitingResourceQueueElement> result = new ArrayList<>();

        boolean needToReassign = oldElement.hasDayAssignments();

        limitingResourceQueueElementDAO.save(oldElement);
        limitingResourceQueueElementDAO.save(newElement);
        toBeSaved.remove(oldElement);
        queuesState.replaceLimitingResourceQueueElement(oldElement, newElement);

        if (needToReassign) {
            result.addAll(assignLimitingResourceQueueElement(newElement));
        }

        HashSet<LimitingResourceQueueDependency> dependencies = new HashSet<>();
        dependencies.addAll(newElement.getDependenciesAsOrigin());
        dependencies.addAll(newElement.getDependenciesAsDestiny());
        toBeSavedDependencies.put(newElement, dependencies);

        markAsModified(newElement);

        return result;
    }

    @Override
    public Set<LimitingResourceQueueElement> assignLimitingResourceQueueElements(
            List<LimitingResourceQueueElement> queueElements) {

        Set<LimitingResourceQueueElement> result = new HashSet<>();
        for (LimitingResourceQueueElement each : queuesState.inTopologicalOrder(queueElements)) {
            result.addAll(assignLimitingResourceQueueElement(each));
        }

        return result;
    }

}