/*
* 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.planner.allocation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.hibernate.Hibernate;
import org.libreplan.business.common.Flagged;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.ProportionalDistributor;
import org.libreplan.business.orders.entities.AggregatedHoursGroup;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.DerivedAllocation;
import org.libreplan.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder;
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.resources.daos.IResourceDAO;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.Machine;
import org.libreplan.business.resources.entities.MachineWorkersConfigurationUnit;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.ResourceEnum;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.web.planner.allocation.AllocationRowsHandler.Warnings;
import org.libreplan.web.planner.order.PlanningStateCreator.PlanningState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zkoss.ganttz.extensions.IContextWithPlannerTask;
/**
* Model for UI operations related to {@link Task}.
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
* @author Javier Moran Rua <jmoran@igalia.com>
*/
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ResourceAllocationModel implements IResourceAllocationModel {
@Autowired
private IResourceDAO resourceDAO;
@Autowired
private IResourcesSearcher searchModel;
private Task task;
private PlanningState planningState;
private AllocationRowsHandler allocationRowsHandler;
private IContextWithPlannerTask<TaskElement> context;
@Autowired
private IAdHocTransactionService transactionService;
private Date currentStartDate;
@Override
@Transactional(readOnly = true)
public void addSpecific(Collection<? extends Resource> resources) {
reassociateResourcesWithSession();
allocationRowsHandler.addSpecificResourceAllocationFor(reloadResources(resources));
}
@SuppressWarnings("unchecked")
private <T extends Resource> List<T> reloadResources(Collection<? extends T> resources) {
List<T> result = new ArrayList<>();
for (T each : resources) {
Resource reloaded = resourceDAO.findExistingEntity(each.getId());
reattachResource(reloaded);
result.add((T) reloaded);
}
return result;
}
@Override
@Transactional(readOnly = true)
public ProportionalDistributor addDefaultAllocations() {
reassociateResourcesWithSession();
List<AggregatedHoursGroup> hoursGroups = task.getAggregatedByCriterions();
int hours[] = new int[hoursGroups.size()];
int i = 0;
for (AggregatedHoursGroup each : hoursGroups) {
hours[i++] = each.getHours();
List<? extends Resource> resourcesFound = searchModel.searchBy(each.getResourceType())
.byCriteria(each.getCriterions()).execute();
boolean added = allocationRowsHandler.addGeneric(each.getResourceType(),
each.getCriterions(), reloadResources(resourcesFound),
each.getHours());
if (!added) {
return null;
}
}
return ProportionalDistributor.create(hours);
}
@Override
@Transactional(readOnly = true)
public void addGeneric(ResourceEnum resourceType,
Collection<? extends Criterion> criteria,
Collection<? extends Resource> resourcesMatched) {
reassociateResourcesWithSession();
List<Resource> reloadResources = reloadResources(resourcesMatched);
allocationRowsHandler.addGeneric(resourceType, criteria, reloadResources);
}
@Override
public void cancel() {
if (currentStartDate != null) {
task.setStartDate(currentStartDate);
}
allocationRowsHandler = null;
currentStartDate = null;
}
@Override
public Flagged<AllocationResult, Warnings> accept() {
if (context != null) {
return applyDateChangesNotificationIfNoFlags(() -> {
stepsBeforeDoingAllocation();
Flagged<AllocationResult, Warnings> allocationResult = allocationRowsHandler.doAllocation();
if (!allocationResult.isFlagged()) {
allocationResult.getValue().applyTo(
planningState.getCurrentScenario(), task);
}
return allocationResult;
});
}
return null;
}
@Override
public void accept(final AllocationResult modifiedAllocationResult) {
if (context != null) {
applyDateChangesNotificationIfNoFlags((IOnTransaction<Flagged<Void, Void>>) () -> {
stepsBeforeDoingAllocation();
modifiedAllocationResult.applyTo(planningState.getCurrentScenario(), task);
return Flagged.justValue(null);
});
}
}
private <V, T extends Flagged<V, ?>> T applyDateChangesNotificationIfNoFlags(IOnTransaction<T> allocationDoer) {
org.zkoss.ganttz.data.Task ganttTask = context.getTask();
T result = transactionService.runOnReadOnlyTransaction(allocationDoer);
if (!result.isFlagged()) {
ganttTask.enforceDependenciesDueToPositionPotentiallyModified();
}
return result;
}
private void stepsBeforeDoingAllocation() {
ensureResourcesAreReadyForDoingAllocation();
removeDeletedAllocations();
}
@Override
@Transactional(readOnly = true)
public <T> T onAllocationContext(
IResourceAllocationContext<T> resourceAllocationContext) {
ensureResourcesAreReadyForDoingAllocation();
return resourceAllocationContext.doInsideTransaction();
}
private void ensureResourcesAreReadyForDoingAllocation() {
Set<Resource> resources = allocationRowsHandler.getAllocationResources();
for (Resource each : resources) {
reattachResource(each);
}
}
private void reassociateResourcesWithSession() {
planningState.reassociateResourcesWithSession();
}
private void removeDeletedAllocations() {
Set<ResourceAllocation<?>> allocationsRequestedToRemove = allocationRowsHandler
.getAllocationsRequestedToRemove();
for (ResourceAllocation<?> resourceAllocation : allocationsRequestedToRemove) {
task.removeResourceAllocation(resourceAllocation);
}
}
@Override
@Transactional(readOnly = true)
public AllocationRowsHandler initAllocationsFor(Task task,
IContextWithPlannerTask<TaskElement> context,
PlanningState planningState) {
this.context = context;
this.task = task;
this.currentStartDate = task.getStartDate();
this.planningState = planningState;
planningState.reassociateResourcesWithSession();
loadDerivedAllocations(this.task.getSatisfiedResourceAllocations());
List<AllocationRow> initialRows = AllocationRow.toRows(task.getNonLimitingResourceAllocations(), searchModel);
allocationRowsHandler = AllocationRowsHandler.create(task, initialRows, createWorkerFinder());
return allocationRowsHandler;
}
private IWorkerFinder createWorkerFinder() {
return requiredCriteria -> {
reassociateResourcesWithSession();
List<Worker> workers = new ArrayList<>();
if (!requiredCriteria.isEmpty()) {
workers = searchModel.searchWorkers().byCriteria(requiredCriteria).execute();
}
return reloadResources(workers);
};
}
private void loadMachine(Machine eachMachine) {
for (MachineWorkersConfigurationUnit eachUnit : eachMachine.getConfigurationUnits()) {
Hibernate.initialize(eachUnit);
}
}
private void loadDerivedAllocations(Set<ResourceAllocation<?>> resourceAllocations) {
for (ResourceAllocation<?> each : resourceAllocations) {
for (DerivedAllocation eachDerived : each.getDerivedAllocations()) {
Hibernate.initialize(eachDerived);
eachDerived.getAssignments();
eachDerived.getAlpha();
eachDerived.getName();
}
}
}
private void reattachResource(Resource resource) {
resourceDAO.reattach(resource);
for (DayAssignment dayAssignment : resource.getAssignments()) {
Hibernate.initialize(dayAssignment);
}
if (resource instanceof Machine) {
loadMachine((Machine) resource);
}
}
@Override
@Transactional(readOnly = true)
public List<AggregatedHoursGroup> getHoursAggregatedByCriterions() {
List<AggregatedHoursGroup> result = task.getTaskSource().getAggregatedByCriterions();
ensuringAccesedPropertiesAreLoaded(result);
return result;
}
private void ensuringAccesedPropertiesAreLoaded(List<AggregatedHoursGroup> result) {
for (AggregatedHoursGroup each : result) {
each.getCriterionsJoinedByComma();
each.getHours();
}
}
@Override
public Integer getOrderHours() {
if (task == null) {
return 0;
}
return AggregatedHoursGroup.sum(task.getAggregatedByCriterions());
}
@Override
public Date getTaskEnd() {
if (task == null) {
return null;
}
return task.getEndDate();
}
@Override
public Date getTaskStart() {
Date result;
if (task == null) {
result = null;
} else {
result = task.getStartDate();
}
return result;
}
}