/*
* 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.limiting.allocation;
import static org.libreplan.web.I18nHelper._;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.hibernate.Hibernate;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.orders.daos.IHoursGroupDAO;
import org.libreplan.business.orders.entities.AggregatedHoursGroup;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.daos.ITaskSourceDAO;
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.entities.LimitingResourceQueueElement;
import org.libreplan.business.resources.daos.ICriterionDAO;
import org.libreplan.business.resources.daos.IResourceDAO;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionType;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.ResourceEnum;
import org.libreplan.web.common.IMessagesForUser;
import org.libreplan.web.common.Level;
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.data.GanttDate;
import org.zkoss.ganttz.extensions.IContextWithPlannerTask;
/**
* Provides logical operations for limiting resource assignations in @{Task}
*
* @author Diego Pino García <dpino@igalia.com>
*/
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class LimitingResourceAllocationModel implements ILimitingResourceAllocationModel {
@Autowired
private IHoursGroupDAO hoursGroupDAO;
@Autowired
private ITaskSourceDAO taskSourceDAO;
@Autowired
private ICriterionDAO criterionDAO;
@Autowired
private ITaskElementDAO taskDAO;
@Autowired
private IResourceDAO resourceDAO;
@Autowired
private IAdHocTransactionService transactionService;
private IContextWithPlannerTask<TaskElement> context;
private Task task;
private PlanningState planningState;
private List<LimitingAllocationRow> limitingAllocationRows = new ArrayList<LimitingAllocationRow>();
private LimitingResourceAllocationController limitingResourceAllocationController;
@Override
@Transactional(readOnly=true)
public void init(IContextWithPlannerTask<TaskElement> context, Task task,
PlanningState planningState) {
this.context = context;
this.task = task;
this.planningState = planningState;
initializeCriteria(task);
limitingAllocationRows = LimitingAllocationRow.toRows(task);
}
private void initializeCriteria(Task task) {
for (ResourceAllocation<?> each: task.getLimitingResourceAllocations()) {
if (isGeneric(each)) {
initializeCriteria((GenericResourceAllocation) each);
}
}
}
private boolean isGeneric(ResourceAllocation<?> resourceAllocation) {
return resourceAllocation instanceof GenericResourceAllocation;
}
private void initializeCriteria(GenericResourceAllocation generic) {
for (Criterion each : generic.getCriterions()) {
initializeCriterion(each);
}
}
private void initializeCriterion(Criterion criterion) {
criterionDAO.reattach(criterion);
Hibernate.initialize(criterion.getType());
}
@Override
@Transactional(readOnly=true)
public Integer getOrderHours() {
if (task == null) {
return 0;
}
return AggregatedHoursGroup.sum(getHoursAggregatedByCriteria());
}
@Override
@Transactional(readOnly = true)
public void addGeneric(ResourceEnum resourceType,
Collection<? extends Criterion> criteria,
Collection<? extends Resource> resources) {
if (resources.isEmpty()) {
getMessagesForUser()
.showMessage(Level.ERROR,
_("there are no resources for required criteria: {0}. " +
"So the generic allocation can't be added",
Criterion.getCaptionFor(resourceType, criteria)));
}
if (resources.size() >= 1) {
if (planningState != null) {
planningState.reassociateResourcesWithSession();
}
addGenericResourceAllocation(resourceType, criteria,
reloadResources(resources));
}
}
private List<Resource> reloadResources(
Collection<? extends Resource> resources) {
List<Resource> result = new ArrayList<Resource>();
for (Resource each: resources) {
result.add(resourceDAO.findExistingEntity(each.getId()));
}
return result;
}
private IMessagesForUser getMessagesForUser() {
return limitingResourceAllocationController.getMessagesForUser();
}
private void addGenericResourceAllocation(ResourceEnum resourceType,
Collection<? extends Criterion> criteria,
Collection<? extends Resource> resources) {
if (isNew(criteria, resources)) {
limitingAllocationRows.clear();
limitingAllocationRows.add(LimitingAllocationRow.create(
resourceType, criteria, resources, task,
LimitingAllocationRow.DEFAULT_PRIORITY));
}
}
private boolean isNew(Collection<? extends Criterion> criteria,
Collection<? extends Resource> resources) {
LimitingAllocationRow allocationRow = getLimitingAllocationRow();
if (allocationRow == null || allocationRow.isSpecific()) {
return true;
}
Set<Long> allocatedResourcesIds = allocationRow.getResourcesIds();
for (Resource each: resources) {
if (!allocatedResourcesIds.contains(each.getId())) {
return true;
}
}
Set<Long> allocatedCriteriaIds = allocationRow.getCriteriaIds();
for (Criterion each: criteria) {
if (!allocatedCriteriaIds.contains(each.getId())) {
return true;
}
}
return false;
}
@Override
@Transactional(readOnly = true)
public void addSpecific(Collection<? extends Resource> resources) {
if (!areAllLimitingResources(resources)) {
getMessagesForUser().showMessage(Level.ERROR,
_("All resources must be queue-based"));
return;
}
if (resources.size() >= 1) {
if (planningState != null) {
planningState.reassociateResourcesWithSession();
}
List<Resource> reloaded = reloadResources(
Collections.singleton(getFirstChild(resources)));
addSpecificResourceAllocation(getFirstChild(reloaded));
}
}
private boolean areAllLimitingResources(
Collection<? extends Resource> resources) {
for (Resource resource : resources) {
if (!resource.isLimitingResource()) {
return false;
}
}
return true;
}
public Resource getFirstChild(Collection<? extends Resource> collection) {
return collection.iterator().next();
}
private void addSpecificResourceAllocation(Resource resource) {
if (isNew(resource)) {
limitingAllocationRows.clear();
LimitingAllocationRow allocationRow = LimitingAllocationRow.create(
resource, task, LimitingAllocationRow.DEFAULT_PRIORITY);
limitingAllocationRows.add(allocationRow);
}
}
private boolean isNew(Resource resource) {
LimitingAllocationRow allocationRow = getLimitingAllocationRow();
if (allocationRow == null || allocationRow.isGeneric()) {
return true;
}
final Resource taskResource = getAssociatedResource();
if (taskResource != null) {
return (!resource.getId().equals(taskResource.getId()));
}
return true;
}
private Resource getAssociatedResource() {
ResourceAllocation<?> resourceAllocation = getAssociatedResourceAllocation();
if (resourceAllocation != null) {
List<Resource> resources = resourceAllocation.getAssociatedResources();
if (resources != null && resources.size() >= 1) {
return (Resource) resources.iterator().next();
}
}
return null;
}
private ResourceAllocation<?> getAssociatedResourceAllocation() {
LimitingAllocationRow allocationRow = getLimitingAllocationRow();
if (allocationRow != null) {
return allocationRow.getResourceAllocation();
}
return null;
}
private LimitingAllocationRow getLimitingAllocationRow() {
if (limitingAllocationRows.size() >= 1) {
return limitingAllocationRows.get(0);
}
return null;
}
@Override
public List<LimitingAllocationRow> getResourceAllocationRows() {
return Collections.unmodifiableList(limitingAllocationRows);
}
@Override
@Transactional(readOnly = true)
public List<AggregatedHoursGroup> getHoursAggregatedByCriteria() {
reattachTaskSource();
List<AggregatedHoursGroup> result = task.getTaskSource()
.getAggregatedByCriterions();
ensuringAccesedPropertiesAreLoaded(result);
return result;
}
private void ensuringAccesedPropertiesAreLoaded(
List<AggregatedHoursGroup> result) {
for (AggregatedHoursGroup each : result) {
each.getCriterionsJoinedByComma();
each.getHours();
}
}
/**
* Re-attach {@link TaskSource}
*/
private void reattachTaskSource() {
TaskSource taskSource = task.getTaskSource();
taskSourceDAO.reattach(taskSource);
Set<HoursGroup> hoursGroups = taskSource.getHoursGroups();
for (HoursGroup hoursGroup : hoursGroups) {
reattachHoursGroup(hoursGroup);
}
}
private void reattachHoursGroup(HoursGroup hoursGroup) {
hoursGroupDAO.reattachUnmodifiedEntity(hoursGroup);
hoursGroup.getPercentage();
reattachCriteria(hoursGroup.getValidCriterions());
}
private void reattachCriteria(Set<Criterion> criterions) {
for (Criterion criterion : criterions) {
reattachCriterion(criterion);
}
}
private void reattachCriterion(Criterion criterion) {
criterionDAO.reattachUnmodifiedEntity(criterion);
criterion.getName();
reattachCriterionType(criterion.getType());
}
private void reattachCriterionType(CriterionType criterionType) {
criterionType.getName();
}
@Override
@Transactional(readOnly=true)
public void confirmSave() {
applyAllocationWithDateChangesNotification(new IOnTransaction<Void>() {
@Override
public Void execute() {
taskDAO.reattach(task);
ResourceAllocation<?> resourceAllocation = getAssociatedResourceAllocation();
if (resourceAllocation != null) {
if (resourceAllocation.isNewObject()) {
addAssociatedLimitingResourceQueueElement(task,
resourceAllocation);
} else {
if (!resourceAllocation.hasAssignments()) {
task.resizeToHours(resourceAllocation
.getIntendedTotalHours());
}
}
}
taskDAO.save(task);
return null;
}
});
}
private void applyAllocationWithDateChangesNotification(
IOnTransaction<?> allocationDoer) {
if (context != null) {
org.zkoss.ganttz.data.Task ganttTask = context.getTask();
transactionService.runOnReadOnlyTransaction(allocationDoer);
ganttTask.enforceDependenciesDueToPositionPotentiallyModified();
} else {
// Update hours of a Task from Limiting Resource view
transactionService.runOnReadOnlyTransaction(allocationDoer);
}
}
private void addAssociatedLimitingResourceQueueElement(Task task, ResourceAllocation<?> resourceAllocation) {
LimitingResourceQueueElement element = LimitingResourceQueueElement.create();
resourceAllocation.setLimitingResourceQueueElement(element);
task.setResourceAllocation(resourceAllocation);
}
@Override
public void setLimitingResourceAllocationController(
LimitingResourceAllocationController limitingResourceAllocationController) {
this.limitingResourceAllocationController = limitingResourceAllocationController;
}
}