/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico 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 García<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;
}
}