/*
* 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-2012 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.reassign;
import static org.libreplan.web.I18nHelper._;
import static org.zkoss.ganttz.util.LongOperationFeedback.and;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.commons.lang.Validate;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.resources.daos.ICriterionTypeDAO;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionType;
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.Component;
import org.zkoss.ganttz.adapters.IDomainAndBeansMapper;
import org.zkoss.ganttz.data.Dependency;
import org.zkoss.ganttz.data.GanttDate;
import org.zkoss.ganttz.data.GanttDiagramGraph;
import org.zkoss.ganttz.data.Task;
import org.zkoss.ganttz.extensions.IContext;
import org.zkoss.ganttz.util.IAction;
import org.zkoss.ganttz.util.LongOperationFeedback;
import org.zkoss.ganttz.util.LongOperationFeedback.IBackGroundOperation;
import org.zkoss.ganttz.util.LongOperationFeedback.IDesktopUpdate;
import org.zkoss.ganttz.util.LongOperationFeedback.IDesktopUpdatesEmitter;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Messagebox;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ReassignCommand implements IReassignCommand {
private PlanningState planningState;
@Autowired
private IAdHocTransactionService transactionService;
@Autowired
private IResourcesSearcher resourcesSearcher;
@Autowired
private ITaskElementDAO taskElementDAO;
@Autowired
private ICriterionTypeDAO criterionTypeDAO;
public interface IConfigurationResult {
public void result(ReassignConfiguration configuration);
}
@Override
public void setState(PlanningState planningState) {
Validate.notNull(planningState);
this.planningState = planningState;
}
@Override
public void doAction(final IContext<TaskElement> context) {
ReassignController.openOn(context.getRelativeTo(),
new IConfigurationResult() {
@Override
public void result(final ReassignConfiguration configuration) {
final List<WithAssociatedEntity> reassignations = getReassignations(
context, configuration);
LongOperationFeedback.progressive(getDesktop(context),
reassignations(context, reassignations));
}
});
}
private IBackGroundOperation<IDesktopUpdate> reassignations(
final IContext<TaskElement> context,
final List<WithAssociatedEntity> reassignations) {
return new IBackGroundOperation<IDesktopUpdate>() {
@Override
public void doOperation(
final IDesktopUpdatesEmitter<IDesktopUpdate> updater) {
updater.doUpdate(busyStart(reassignations.size()));
GanttDiagramGraph<Task, Dependency>.DeferedNotifier notifications = null;
try {
GanttDiagramGraph<Task, Dependency> ganttDiagramGraph = context
.getGanttDiagramGraph();
notifications = ganttDiagramGraph
.manualNotificationOn(doReassignations(
ganttDiagramGraph, reassignations, updater));
} finally {
if (notifications != null) {
// null if error
updater.doUpdate(and(doNotifications(notifications),
reloadCharts(context), busyEnd(),
tellUserOnEnd(context, Messagebox.INFORMATION,
new Callable<String>() {
@Override
public String call() {
return _("{0} reassignations finished",
reassignations.size());
}
})));
} else {
updater.doUpdate(and(busyEnd(),
tellUserOnEnd(context, Messagebox.EXCLAMATION,
new Callable<String>() {
@Override
public String call() {
return _("Assignments could not be completed");
}
})));
}
}
}
};
}
private IAction doReassignations(final GanttDiagramGraph<Task, Dependency> diagramGraph,
final List<WithAssociatedEntity> reassignations,
final IDesktopUpdatesEmitter<IDesktopUpdate> updater) {
return new IAction() {
@Override
public void doAction() {
int i = 1;
final int total = reassignations.size();
for (final WithAssociatedEntity each : reassignations) {
Task ganttTask = each.ganntTask;
GanttDate previousStart = ganttTask.getBeginDate();
GanttDate previousEnd = ganttTask.getEndDate();
transactionService
.runOnReadOnlyTransaction(reassignmentTransaction(each));
diagramGraph.enforceRestrictions(each.ganntTask);
ganttTask.enforceDependenciesDueToPositionPotentiallyModified();
ganttTask.updateSizeDueToDateChanges(previousStart, previousEnd);
updater.doUpdate(showCompleted(i, total));
i++;
}
}
};
}
private IDesktopUpdate busyStart(final int total) {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
Clients.showBusy(_("Doing {0} reassignations", total), true);
}
};
}
private IDesktopUpdate showCompleted(final int number, final int total) {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
Clients.showBusy(_("Done {0} of {1}", number, total), true);
}
};
}
private IDesktopUpdate reloadCharts(final IContext<?> context) {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
context.reloadCharts();
}
};
}
private IDesktopUpdate doNotifications(
final GanttDiagramGraph<Task, Dependency>.DeferedNotifier notifier) {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
notifier.doNotifications();
}
};
}
private IDesktopUpdate busyEnd() {
return new IDesktopUpdate() {
@Override
public void doUpdate() {
Clients.showBusy(null, false);
}
};
}
private IDesktopUpdate tellUserOnEnd(final IContext<TaskElement> context,
String icon, final Callable<String> message) {
// using callable so the message is built inside a zk execution and the
// locale is correctly retrieved
return new IDesktopUpdate() {
@Override
public void doUpdate() {
final org.zkoss.zk.ui.Component relativeTo = context
.getRelativeTo();
final String eventName = "onLater";
Events.echoEvent(eventName, relativeTo, null);
relativeTo.addEventListener(eventName, new EventListener() {
@Override
public void onEvent(Event event) {
relativeTo.removeEventListener(eventName, this);
try {
Messagebox.show(resolve(message),
_("Reassignation"),
Messagebox.OK, Messagebox.INFORMATION);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
};
}
private <T> T resolve(Callable<T> callable) {
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static class WithAssociatedEntity {
static WithAssociatedEntity create(
IDomainAndBeansMapper<TaskElement> mapper, Task each) {
return new WithAssociatedEntity(mapper
.findAssociatedDomainObject(each), each);
}
private TaskElement domainEntity;
private Task ganntTask;
WithAssociatedEntity(TaskElement domainEntity, Task ganntTask) {
Validate.notNull(domainEntity);
Validate.notNull(ganntTask);
this.domainEntity = domainEntity;
this.ganntTask = ganntTask;
}
}
private List<WithAssociatedEntity> getReassignations(
IContext<TaskElement> context, ReassignConfiguration configuration) {
Validate.notNull(configuration);
List<Task> taskToReassign = configuration.filterForReassignment(context
.getTasksOrderedByStartDate());
return withEntities(context.getMapper(), taskToReassign);
}
private List<WithAssociatedEntity> withEntities(
IDomainAndBeansMapper<TaskElement> mapper,
List<Task> forReassignment) {
List<WithAssociatedEntity> result = new ArrayList<WithAssociatedEntity>();
for (Task each : forReassignment) {
result.add(WithAssociatedEntity.create(mapper, each));
}
return result;
}
private IOnTransaction<Void> reassignmentTransaction(
final WithAssociatedEntity withAssociatedEntity) {
return new IOnTransaction<Void>() {
@Override
public Void execute() {
reattach(withAssociatedEntity);
reassign(withAssociatedEntity.domainEntity);
return null;
}
};
}
private void reattach(WithAssociatedEntity each) {
planningState.reassociateResourcesWithSession();
Set<Long> idsOfTypesAlreadyAttached = new HashSet<Long>();
taskElementDAO.reattach(each.domainEntity);
Set<ResourceAllocation<?>> resourceAllocations = each.domainEntity
.getSatisfiedResourceAllocations();
List<GenericResourceAllocation> generic = ResourceAllocation.getOfType(
GenericResourceAllocation.class, resourceAllocations);
reattachCriterionTypesToAvoidLazyInitializationExceptionOnType(
idsOfTypesAlreadyAttached, generic);
}
private void reattachCriterionTypesToAvoidLazyInitializationExceptionOnType(
Set<Long> idsOfTypesAlreadyAttached,
List<GenericResourceAllocation> generic) {
for (GenericResourceAllocation eachGenericAllocation : generic) {
Set<Criterion> criterions = eachGenericAllocation.getCriterions();
for (Criterion eachCriterion : criterions) {
CriterionType type = eachCriterion.getType();
if (!idsOfTypesAlreadyAttached.contains(type.getId())) {
idsOfTypesAlreadyAttached.add(type.getId());
criterionTypeDAO.reattachUnmodifiedEntity(type);
}
}
}
}
private void reassign(TaskElement taskElement) {
org.libreplan.business.planner.entities.Task t = (org.libreplan.business.planner.entities.Task) taskElement;
t.reassignAllocationsWithNewResources(
planningState.getCurrentScenario(), resourcesSearcher);
}
@Override
public String getName() {
return _("Reassign");
}
@Override
public String getImage() {
return "/common/img/ico_reassign.png";
}
private Desktop getDesktop(final IContext<TaskElement> context) {
return context.getRelativeTo().getDesktop();
}
@Override
public boolean isDisabled() {
return false;
}
@Override
public boolean isPlannerCommand() {
return true;
}
}