diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java index cf5013751..6fedce688 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java @@ -89,7 +89,7 @@ import org.navalplanner.business.workingday.EffortDuration; import org.navalplanner.business.workingday.IntraDayDate; import org.navalplanner.web.common.concurrentdetection.OnConcurrentModification; import org.navalplanner.web.limitingresources.QueuesState.Edge; -import org.navalplanner.web.planner.order.SaveCommand; +import org.navalplanner.web.planner.order.SaveCommandBuilder; import org.navalplanner.web.security.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; @@ -922,7 +922,7 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { } } updateEndDateForParentTasks(); - SaveCommand.dontPoseAsTransientAndChildrenObjects(getAllocations(toBeSaved)); + SaveCommandBuilder.dontPoseAsTransientAndChildrenObjects(getAllocations(toBeSaved)); toBeSaved.clear(); parentElementsToBeUpdated.clear(); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/ISaveCommand.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/ISaveCommand.java index 9bf06575e..0b34a8c0f 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/ISaveCommand.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/ISaveCommand.java @@ -22,12 +22,10 @@ package org.navalplanner.web.planner.order; import org.navalplanner.business.planner.entities.TaskElement; -import org.navalplanner.web.planner.order.PlanningStateCreator.PlanningState; -import org.zkoss.ganttz.adapters.PlannerConfiguration; import org.zkoss.ganttz.extensions.ICommand; /** - * Contract for {@link SaveCommand}
+ * Contract for {@link SaveCommandBuilder}
* @author Óscar González Fernández */ public interface ISaveCommand extends ICommand { @@ -36,10 +34,6 @@ public interface ISaveCommand extends ICommand { void onAfterSave(); } - public void setState(PlanningState planningState); - - public void setConfiguration(PlannerConfiguration configuration); - public void addListener(IAfterSaveListener listener); public void removeListener(IAfterSaveListener listener); diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/OrderPlanningModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/OrderPlanningModel.java index ae6a2e2b4..c0ee557f5 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/OrderPlanningModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/OrderPlanningModel.java @@ -223,7 +223,7 @@ public class OrderPlanningModel implements IOrderPlanningModel { private IScenarioManager scenarioManager; @Autowired - private ISaveCommand saveCommand; + private SaveCommandBuilder saveCommandBuilder; @Autowired private IReassignCommand reassignCommand; @@ -1019,9 +1019,7 @@ public class OrderPlanningModel implements IOrderPlanningModel { private ISaveCommand buildSaveCommand( PlannerConfiguration configuration) { - saveCommand.setConfiguration(configuration); - saveCommand.setState(planningState); - return saveCommand; + return saveCommandBuilder.build(planningState, configuration); } private ICommand buildReassigningCommand() { diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/SaveCommand.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/SaveCommand.java deleted file mode 100644 index baeea1a0c..000000000 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/SaveCommand.java +++ /dev/null @@ -1,639 +0,0 @@ -/* - * This file is part of NavalPlan - * - * 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 . - */ - -package org.navalplanner.web.planner.order; - -import static org.navalplanner.business.planner.limiting.entities.LimitingResourceQueueDependency.toQueueDependencyType; -import static org.navalplanner.web.I18nHelper._; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.SortedSet; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.joda.time.LocalDate; -import org.navalplanner.business.advance.entities.AdvanceAssignment; -import org.navalplanner.business.advance.entities.AdvanceMeasurement; -import org.navalplanner.business.advance.entities.DirectAdvanceAssignment; -import org.navalplanner.business.common.IAdHocTransactionService; -import org.navalplanner.business.common.IOnTransaction; -import org.navalplanner.business.common.exceptions.InstanceNotFoundException; -import org.navalplanner.business.common.exceptions.ValidationException; -import org.navalplanner.business.orders.daos.IOrderDAO; -import org.navalplanner.business.orders.daos.IOrderElementDAO; -import org.navalplanner.business.orders.entities.OrderElement; -import org.navalplanner.business.planner.daos.IConsolidationDAO; -import org.navalplanner.business.planner.daos.ISubcontractedTaskDataDAO; -import org.navalplanner.business.planner.daos.ITaskElementDAO; -import org.navalplanner.business.planner.daos.ITaskSourceDAO; -import org.navalplanner.business.planner.entities.DayAssignment; -import org.navalplanner.business.planner.entities.Dependency; -import org.navalplanner.business.planner.entities.DerivedAllocation; -import org.navalplanner.business.planner.entities.DerivedDayAssignment; -import org.navalplanner.business.planner.entities.DerivedDayAssignmentsContainer; -import org.navalplanner.business.planner.entities.ResourceAllocation; -import org.navalplanner.business.planner.entities.Task; -import org.navalplanner.business.planner.entities.TaskElement; -import org.navalplanner.business.planner.entities.TaskGroup; -import org.navalplanner.business.planner.entities.consolidations.CalculatedConsolidatedValue; -import org.navalplanner.business.planner.entities.consolidations.CalculatedConsolidation; -import org.navalplanner.business.planner.entities.consolidations.ConsolidatedValue; -import org.navalplanner.business.planner.entities.consolidations.Consolidation; -import org.navalplanner.business.planner.entities.consolidations.NonCalculatedConsolidatedValue; -import org.navalplanner.business.planner.entities.consolidations.NonCalculatedConsolidation; -import org.navalplanner.business.planner.limiting.daos.ILimitingResourceQueueDependencyDAO; -import org.navalplanner.business.planner.limiting.entities.LimitingResourceQueueDependency; -import org.navalplanner.business.planner.limiting.entities.LimitingResourceQueueElement; -import org.navalplanner.web.common.concurrentdetection.OnConcurrentModification; -import org.navalplanner.web.planner.TaskElementAdapter; -import org.navalplanner.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.DomainDependency; -import org.zkoss.ganttz.adapters.IAdapterToTaskFundamentalProperties; -import org.zkoss.ganttz.adapters.PlannerConfiguration; -import org.zkoss.ganttz.data.ConstraintCalculator; -import org.zkoss.ganttz.data.DependencyType.Point; -import org.zkoss.ganttz.data.GanttDate; -import org.zkoss.ganttz.data.constraint.Constraint; -import org.zkoss.ganttz.extensions.IContext; -import org.zkoss.zul.Messagebox; - -@Component -@Scope(BeanDefinition.SCOPE_PROTOTYPE) -/** - * A command that saves the changes in the taskElements. - * It can be considered the final step in the conversation
- * - * In the save operation it is also kept the consistency of the - * LimitingResourceQueueDependencies with the Dependecies between - * the task of the planning gantt. - * - * @author Óscar González Fernández - * @author Javier Moran Rua - */ -@OnConcurrentModification(goToPage = "/planner/index.zul;company_scheduling") -public class SaveCommand implements ISaveCommand { - - private static final Log LOG = LogFactory.getLog(SaveCommand.class); - - @Autowired - private IConsolidationDAO consolidationDAO; - - @Autowired - private ITaskElementDAO taskElementDAO; - - @Autowired - private ITaskSourceDAO taskSourceDAO; - - @Autowired - private IOrderElementDAO orderElementDAO; - - @Autowired - private IOrderDAO orderDAO; - - @Autowired - private ISubcontractedTaskDataDAO subcontractedTaskDataDAO; - - @Autowired - private ILimitingResourceQueueDependencyDAO limitingResourceQueueDependencyDAO; - - private PlanningState state; - - private PlannerConfiguration configuration; - - private ConstraintCalculator constraintCalculator; - - @Autowired - private IAdHocTransactionService transactionService; - - private List listeners = new ArrayList(); - - private IAdapterToTaskFundamentalProperties adapter; - - - @Override - public void setState(PlanningState state) { - this.state = state; - } - - @Override - public void setConfiguration(PlannerConfiguration configuration) { - this.configuration = configuration; - this.adapter = configuration.getAdapter(); - this.constraintCalculator = new ConstraintCalculator( - configuration.isScheduleBackwards()) { - - @Override - protected GanttDate getStartDate(TaskElement vertex) { - return TaskElementAdapter - .toGantt(vertex.getIntraDayStartDate()); - } - - @Override - protected GanttDate getEndDate(TaskElement vertex) { - return TaskElementAdapter.toGantt(vertex.getIntraDayEndDate()); - } - }; - } - - @Override - public void doAction(IContext context) { - if (state.getScenarioInfo().isUsingTheOwnerScenario() - || userAcceptsCreateANewOrderVersion()) { - transactionService.runOnTransaction(new IOnTransaction() { - @Override - public Void execute() { - doTheSaving(); - return null; - } - }); - state.getScenarioInfo().afterCommit(); - fireAfterSave(); - notifyUserThatSavingIsDone(); - } - } - - private void fireAfterSave() { - for (IAfterSaveListener listener : listeners) { - listener.onAfterSave(); - } - } - - private void notifyUserThatSavingIsDone() { - try { - Messagebox.show(_("Scheduling saved"), _("Information"), - Messagebox.OK, Messagebox.INFORMATION); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - private void doTheSaving() { - state.getScenarioInfo().saveVersioningInfo(); - saveTasksToSave(); - removeTasksToRemove(); - saveAndDontPoseAsTransientOrderElements(); - subcontractedTaskDataDAO.removeOrphanedSubcontractedTaskData(); - } - - private void removeTasksToRemove() { - for (TaskElement taskElement : state.getToRemove()) { - if (taskElementDAO.exists(taskElement.getId())) { - // it might have already been saved in a previous save action - try { - taskElementDAO.remove(taskElement.getId()); - } catch (InstanceNotFoundException e) { - throw new RuntimeException(e); - } - } - } - } - - private void saveTasksToSave() { - for (TaskElement taskElement : state.getTasksToSave()) { - removeEmptyConsolidation(taskElement); - updateLimitingResourceQueueElementDates(taskElement); - taskElementDAO.save(taskElement); - if (taskElement.getTaskSource() != null - && taskElement.getTaskSource().isNewObject()) { - saveTaskSources(taskElement); - } - // Recursive iteration to put all the tasks of the - // gantt as transiet - dontPoseAsTransient(taskElement); - } - saveRootTaskIfNecessary(); - } - - private void saveRootTaskIfNecessary() { - if (!state.getTasksToSave().isEmpty()) { - TaskGroup rootTask = state.getRootTask(); - - updateRootTaskPosition(rootTask); - taskElementDAO.save(rootTask); - } - } - - private void updateRootTaskPosition(TaskGroup rootTask) { - final Date min = minDate(state.getTasksToSave()); - if (min != null) { - rootTask.setStartDate(min); - } - final Date max = maxDate(state.getTasksToSave()); - if (max != null) { - rootTask.setEndDate(max); - } - } - - private void saveTaskSources(TaskElement taskElement) { - taskSourceDAO.save(taskElement.getTaskSource()); - taskElement.getTaskSource().dontPoseAsTransientObjectAnymore(); - if (taskElement.isLeaf()) { - return; - } - for (TaskElement each : taskElement.getChildren()) { - saveTaskSources(each); - } - } - - private void updateLimitingResourceQueueElementDates(TaskElement taskElement) { - if (taskElement.isLimiting()) { - Task task = (Task) taskElement; - updateLimitingResourceQueueElementDates(task); - } else if (!taskElement.isLeaf()) { - for (TaskElement each : taskElement.getChildren()) { - updateLimitingResourceQueueElementDates(each); - } - } - } - - private void updateLimitingResourceQueueElementDates(Task task) { - try { - LimitingResourceQueueElement limiting = task - .getAssociatedLimitingResourceQueueElementIfAny(); - - GanttDate earliestStart = resolveConstraints(task, Point.START); - GanttDate earliestEnd = resolveConstraints(task, Point.END); - - limiting.updateDates(TaskElementAdapter.toIntraDay(earliestStart), - TaskElementAdapter.toIntraDay(earliestEnd)); - } catch (Exception e) { - // if this fails all the saving shouldn't fail - LOG.error( - "error updating associated LimitingResourceQueueElement for task: " - + task, e); - } - } - - private GanttDate resolveConstraints(Task task, Point point) { - List> dependencyConstraints = toConstraints( - adapter.getIncomingDependencies(task), point); - List> taskConstraints = getTaskConstraints(task); - - boolean dependenciesHavePriority = configuration - .isDependenciesConstraintsHavePriority(); - if (dependenciesHavePriority) { - return Constraint - . initialValue( - TaskElementAdapter.toGantt(getOrderInitDate())) - .withConstraints(taskConstraints) - .withConstraints(dependencyConstraints) - .applyWithoutFinalCheck(); - } else { - return Constraint - . initialValue( - TaskElementAdapter.toGantt(getOrderInitDate())) - .withConstraints(dependencyConstraints) - .withConstraints(taskConstraints) - .applyWithoutFinalCheck(); - } - } - - private List> getTaskConstraints(Task task) { - return TaskElementAdapter.getStartConstraintsFor(task, getOrderInitDate()); - } - - private LocalDate getOrderInitDate() { - return LocalDate.fromDateFields(state.getRootTask().getOrderElement() - .getInitDate()); - } - - private List> toConstraints( - List> incomingDependencies, - Point point) { - List> result = new ArrayList>(); - for (DomainDependency each : incomingDependencies) { - result.addAll(constraintCalculator.getConstraints(each, point)); - } - return result; - } - - private void removeEmptyConsolidation(TaskElement taskElement) { - if ((taskElement.isLeaf()) && (!taskElement.isMilestone())) { - Consolidation consolidation = ((Task) taskElement) - .getConsolidation(); - if ((consolidation != null) - && (isEmptyConsolidation(consolidation))) { - if (!consolidation.isNewObject()) { - try { - consolidationDAO.remove(consolidation.getId()); - } catch (InstanceNotFoundException e) { - throw new RuntimeException(e); - } - } - ((Task) taskElement).setConsolidation(null); - } - } - } - - private boolean isEmptyConsolidation(final Consolidation consolidation) { - return transactionService - .runOnTransaction(new IOnTransaction() { - @Override - public Boolean execute() { - - consolidationDAO.reattach(consolidation); - if (consolidation instanceof CalculatedConsolidation) { - SortedSet consolidatedValues = ((CalculatedConsolidation) consolidation) - .getCalculatedConsolidatedValues(); - return consolidatedValues.isEmpty(); - } - if (consolidation instanceof NonCalculatedConsolidation) { - SortedSet consolidatedValues = ((NonCalculatedConsolidation) consolidation) - .getNonCalculatedConsolidatedValues(); - return consolidatedValues.isEmpty(); - } - return false; - - } - }); - } - - // newly added TaskElement such as milestones must be called - // dontPoseAsTransientObjectAnymore - private void dontPoseAsTransient(TaskElement taskElement) { - if (taskElement.isNewObject()) { - taskElement.dontPoseAsTransientObjectAnymore(); - } - dontPoseAsTransient(taskElement.getDependenciesWithThisOrigin()); - dontPoseAsTransient(taskElement.getDependenciesWithThisDestination()); - Set> resourceAllocations = taskElement - .getAllResourceAllocations(); - dontPoseAsTransientAndChildrenObjects(resourceAllocations); - if (!taskElement.isLeaf()) { - for (TaskElement each : taskElement.getChildren()) { - dontPoseAsTransient(each); - } - } - if (taskElement instanceof Task) { - updateLimitingQueueDependencies((Task) taskElement); - dontPoseAsTransient(((Task) taskElement).getConsolidation()); - } - } - - private void dontPoseAsTransient( - Collection dependencies) { - for (Dependency each : dependencies) { - each.dontPoseAsTransientObjectAnymore(); - } - } - - private void updateLimitingQueueDependencies(Task t) { - - for (Dependency each : t.getDependenciesWithThisOrigin()) { - addLimitingDependencyIfNeeded(each); - removeLimitingDependencyIfNeeded(each); - } - } - - private void addLimitingDependencyIfNeeded(Dependency d) { - if (d.isDependencyBetweenLimitedAllocatedTasks() - && !d.hasLimitedQueueDependencyAssociated()) { - LimitingResourceQueueElement origin = calculateQueueElementFromDependency((Task) d - .getOrigin()); - LimitingResourceQueueElement destiny = calculateQueueElementFromDependency((Task) d - .getDestination()); - - LimitingResourceQueueDependency queueDependency = LimitingResourceQueueDependency - .create(origin, destiny, d, - toQueueDependencyType(d.getType())); - d.setQueueDependency(queueDependency); - limitingResourceQueueDependencyDAO.save(queueDependency); - queueDependency.dontPoseAsTransientObjectAnymore(); - } - } - - private LimitingResourceQueueElement calculateQueueElementFromDependency( - Task t) { - - LimitingResourceQueueElement result = null; - // TODO: Improve this method: One Task can only have one - // limiting resource allocation - Set> allocations = t - .getLimitingResourceAllocations(); - - if (allocations.isEmpty() || allocations.size() != 1) { - throw new ValidationException("Incorrect limiting resource " - + "allocation configuration"); - } - - for (ResourceAllocation r : allocations) { - result = r.getLimitingResourceQueueElement(); - } - - return result; - } - - private void removeLimitingDependencyIfNeeded(Dependency d) { - if (!d.isDependencyBetweenLimitedAllocatedTasks() - && (d.hasLimitedQueueDependencyAssociated())) { - LimitingResourceQueueDependency queueDependency = d - .getQueueDependency(); - queueDependency.getHasAsOrigin().remove(queueDependency); - queueDependency.getHasAsDestiny().remove(queueDependency); - d.setQueueDependency(null); - try { - limitingResourceQueueDependencyDAO.remove(queueDependency - .getId()); - } catch (InstanceNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException("Trying to delete instance " - + " does not exist"); - } - } - } - - private void dontPoseAsTransient(OrderElement orderElement) { - OrderElement order = (OrderElement) orderDAO - .loadOrderAvoidingProxyFor(orderElement); - order.dontPoseAsTransientObjectAnymore(); - dontPoseAsTransientAdvances(order.getDirectAdvanceAssignments()); - dontPoseAsTransientAdvances(order.getIndirectAdvanceAssignments()); - - for (OrderElement child : order.getAllChildren()) { - child.dontPoseAsTransientObjectAnymore(); - dontPoseAsTransientAdvances(child.getDirectAdvanceAssignments()); - dontPoseAsTransientAdvances(child.getIndirectAdvanceAssignments()); - } - } - - private void dontPoseAsTransientAdvances( - Set advances) { - for (AdvanceAssignment advance : advances) { - advance.dontPoseAsTransientObjectAnymore(); - if (advance instanceof DirectAdvanceAssignment) { - dontPoseAsTransientMeasure(((DirectAdvanceAssignment) advance) - .getAdvanceMeasurements()); - } - } - } - - private void dontPoseAsTransientMeasure(SortedSet list) { - for (AdvanceMeasurement measure : list) { - measure.dontPoseAsTransientObjectAnymore(); - } - } - - private void dontPoseAsTransient(Consolidation consolidation) { - if (consolidation != null) { - consolidation.dontPoseAsTransientObjectAnymore(); - if (consolidation.isCalculated()) { - dontPoseAsTransient(((CalculatedConsolidation) consolidation) - .getCalculatedConsolidatedValues()); - } else { - dontPoseAsTransient(((NonCalculatedConsolidation) consolidation) - .getNonCalculatedConsolidatedValues()); - } - } - } - - private void saveAndDontPoseAsTransientOrderElements() { - for (TaskElement taskElement : state.getTasksToSave()) { - if (taskElement.getOrderElement() != null) { - orderElementDAO.save(taskElement.getOrderElement()); - dontPoseAsTransient(taskElement.getOrderElement()); - } - } - } - - private void dontPoseAsTransient( - SortedSet values) { - for (ConsolidatedValue value : values) { - value.dontPoseAsTransientObjectAnymore(); - } - } - - public static void dontPoseAsTransientAndChildrenObjects( - Collection> resourceAllocations) { - for (ResourceAllocation each : resourceAllocations) { - each.dontPoseAsTransientObjectAnymore(); - each.makeAssignmentsContainersDontPoseAsTransientAnyMore(); - for (DayAssignment eachAssignment : each.getAssignments()) { - eachAssignment.dontPoseAsTransientObjectAnymore(); - } - for (DerivedAllocation eachDerived : each.getDerivedAllocations()) { - eachDerived.dontPoseAsTransientObjectAnymore(); - Collection containers = eachDerived - .getContainers(); - for (DerivedDayAssignmentsContainer eachContainer : containers) { - eachContainer.dontPoseAsTransientObjectAnymore(); - } - for (DerivedDayAssignment eachAssignment : eachDerived - .getAssignments()) { - eachAssignment.dontPoseAsTransientObjectAnymore(); - } - } - dontPoseAsTransient(each.getLimitingResourceQueueElement()); - } - } - - private static void dontPoseAsTransient(LimitingResourceQueueElement element) { - if (element != null) { - for (LimitingResourceQueueDependency d : element - .getDependenciesAsOrigin()) { - d.dontPoseAsTransientObjectAnymore(); - } - for (LimitingResourceQueueDependency d : element - .getDependenciesAsDestiny()) { - d.dontPoseAsTransientObjectAnymore(); - } - element.dontPoseAsTransientObjectAnymore(); - } - } - - private Date maxDate(Collection tasksToSave) { - List endDates = toEndDates(tasksToSave); - return endDates.isEmpty() ? null : Collections.max(endDates); - } - - private List toEndDates(Collection tasksToSave) { - List result = new ArrayList(); - for (TaskElement taskElement : tasksToSave) { - Date endDate = taskElement.getEndDate(); - if (endDate != null) { - result.add(endDate); - } else { - LOG.warn("the task" + taskElement + " has null end date"); - } - } - return result; - } - - private Date minDate(Collection tasksToSave) { - List startDates = toStartDates(tasksToSave); - return startDates.isEmpty() ? null : Collections.min(startDates); - } - - private List toStartDates( - Collection tasksToSave) { - List result = new ArrayList(); - for (TaskElement taskElement : tasksToSave) { - Date startDate = taskElement.getStartDate(); - if (startDate != null) { - result.add(startDate); - } else { - LOG.warn("the task" + taskElement + " has null start date"); - } - } - return result; - } - - @Override - public String getName() { - return _("Save"); - } - - @Override - public void addListener(IAfterSaveListener listener) { - listeners.add(listener); - } - - @Override - public void removeListener(IAfterSaveListener listener) { - listeners.remove(listener); - } - - @Override - public String getImage() { - return "/common/img/ico_save.png"; - } - - private boolean userAcceptsCreateANewOrderVersion() { - try { - int status = Messagebox - .show(_("Confirm creating a new project version for this scenario and derived. Are you sure?"), - _("New project version"), Messagebox.OK - | Messagebox.CANCEL, Messagebox.QUESTION); - return (Messagebox.OK == status); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/SaveCommandBuilder.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/SaveCommandBuilder.java new file mode 100644 index 000000000..e3716eff4 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/order/SaveCommandBuilder.java @@ -0,0 +1,658 @@ +/* + * This file is part of NavalPlan + * + * 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 . + */ + +package org.navalplanner.web.planner.order; + +import static org.navalplanner.business.planner.limiting.entities.LimitingResourceQueueDependency.toQueueDependencyType; +import static org.navalplanner.web.I18nHelper._; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.LocalDate; +import org.navalplanner.business.advance.entities.AdvanceAssignment; +import org.navalplanner.business.advance.entities.AdvanceMeasurement; +import org.navalplanner.business.advance.entities.DirectAdvanceAssignment; +import org.navalplanner.business.common.IAdHocTransactionService; +import org.navalplanner.business.common.IOnTransaction; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.orders.daos.IOrderDAO; +import org.navalplanner.business.orders.daos.IOrderElementDAO; +import org.navalplanner.business.orders.entities.OrderElement; +import org.navalplanner.business.planner.daos.IConsolidationDAO; +import org.navalplanner.business.planner.daos.ISubcontractedTaskDataDAO; +import org.navalplanner.business.planner.daos.ITaskElementDAO; +import org.navalplanner.business.planner.daos.ITaskSourceDAO; +import org.navalplanner.business.planner.entities.DayAssignment; +import org.navalplanner.business.planner.entities.Dependency; +import org.navalplanner.business.planner.entities.DerivedAllocation; +import org.navalplanner.business.planner.entities.DerivedDayAssignment; +import org.navalplanner.business.planner.entities.DerivedDayAssignmentsContainer; +import org.navalplanner.business.planner.entities.ResourceAllocation; +import org.navalplanner.business.planner.entities.Task; +import org.navalplanner.business.planner.entities.TaskElement; +import org.navalplanner.business.planner.entities.TaskGroup; +import org.navalplanner.business.planner.entities.consolidations.CalculatedConsolidatedValue; +import org.navalplanner.business.planner.entities.consolidations.CalculatedConsolidation; +import org.navalplanner.business.planner.entities.consolidations.ConsolidatedValue; +import org.navalplanner.business.planner.entities.consolidations.Consolidation; +import org.navalplanner.business.planner.entities.consolidations.NonCalculatedConsolidatedValue; +import org.navalplanner.business.planner.entities.consolidations.NonCalculatedConsolidation; +import org.navalplanner.business.planner.limiting.daos.ILimitingResourceQueueDependencyDAO; +import org.navalplanner.business.planner.limiting.entities.LimitingResourceQueueDependency; +import org.navalplanner.business.planner.limiting.entities.LimitingResourceQueueElement; +import org.navalplanner.web.common.concurrentdetection.ConcurrentModificationHandling; +import org.navalplanner.web.planner.TaskElementAdapter; +import org.navalplanner.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.DomainDependency; +import org.zkoss.ganttz.adapters.IAdapterToTaskFundamentalProperties; +import org.zkoss.ganttz.adapters.PlannerConfiguration; +import org.zkoss.ganttz.data.ConstraintCalculator; +import org.zkoss.ganttz.data.DependencyType.Point; +import org.zkoss.ganttz.data.GanttDate; +import org.zkoss.ganttz.data.constraint.Constraint; +import org.zkoss.ganttz.extensions.IContext; +import org.zkoss.zul.Messagebox; + +/** + * Builds a command that saves the changes in the taskElements. It can be + * considered the final step in the conversation
+ * + * In the save operation it is also kept the consistency of the + * LimitingResourceQueueDependencies with the Dependecies between the task of + * the planning gantt. + * + * @author Óscar González Fernández + * @author Javier Moran Rua + */ +@Component +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class SaveCommandBuilder { + + private static final Log LOG = LogFactory.getLog(SaveCommandBuilder.class); + + public ISaveCommand build(PlanningState planningState, + PlannerConfiguration plannerConfiguration) { + SaveCommandImpl result = new SaveCommandImpl(planningState, + plannerConfiguration); + + return ConcurrentModificationHandling.addHandling( + "/planner/index.zul;company_scheduling", + ISaveCommand.class, result); + } + + public static void dontPoseAsTransientAndChildrenObjects( + Collection> resourceAllocations) { + for (ResourceAllocation each : resourceAllocations) { + each.dontPoseAsTransientObjectAnymore(); + each.makeAssignmentsContainersDontPoseAsTransientAnyMore(); + for (DayAssignment eachAssignment : each.getAssignments()) { + eachAssignment.dontPoseAsTransientObjectAnymore(); + } + for (DerivedAllocation eachDerived : each + .getDerivedAllocations()) { + eachDerived.dontPoseAsTransientObjectAnymore(); + Collection containers = eachDerived + .getContainers(); + for (DerivedDayAssignmentsContainer eachContainer : containers) { + eachContainer.dontPoseAsTransientObjectAnymore(); + } + for (DerivedDayAssignment eachAssignment : eachDerived + .getAssignments()) { + eachAssignment.dontPoseAsTransientObjectAnymore(); + } + } + dontPoseAsTransient(each.getLimitingResourceQueueElement()); + } + } + + private static void dontPoseAsTransient( + LimitingResourceQueueElement element) { + if (element != null) { + for (LimitingResourceQueueDependency d : element + .getDependenciesAsOrigin()) { + d.dontPoseAsTransientObjectAnymore(); + } + for (LimitingResourceQueueDependency d : element + .getDependenciesAsDestiny()) { + d.dontPoseAsTransientObjectAnymore(); + } + element.dontPoseAsTransientObjectAnymore(); + } + } + + @Autowired + private IConsolidationDAO consolidationDAO; + + @Autowired + private ITaskElementDAO taskElementDAO; + + @Autowired + private ITaskSourceDAO taskSourceDAO; + + @Autowired + private IOrderElementDAO orderElementDAO; + + @Autowired + private IOrderDAO orderDAO; + + @Autowired + private ISubcontractedTaskDataDAO subcontractedTaskDataDAO; + + @Autowired + private ILimitingResourceQueueDependencyDAO limitingResourceQueueDependencyDAO; + + @Autowired + private IAdHocTransactionService transactionService; + + private class SaveCommandImpl implements ISaveCommand { + + private PlanningState state; + + private PlannerConfiguration configuration; + + private ConstraintCalculator constraintCalculator; + + private IAdapterToTaskFundamentalProperties adapter; + + private List listeners = new ArrayList(); + + public SaveCommandImpl(PlanningState planningState, + PlannerConfiguration configuration) { + this.state = planningState; + this.configuration = configuration; + this.adapter = configuration.getAdapter(); + this.constraintCalculator = new ConstraintCalculator( + configuration.isScheduleBackwards()) { + + @Override + protected GanttDate getStartDate(TaskElement vertex) { + return TaskElementAdapter.toGantt(vertex + .getIntraDayStartDate()); + } + + @Override + protected GanttDate getEndDate(TaskElement vertex) { + return TaskElementAdapter.toGantt(vertex + .getIntraDayEndDate()); + } + }; + } + + @Override + public void doAction(IContext context) { + if (state.getScenarioInfo().isUsingTheOwnerScenario() + || userAcceptsCreateANewOrderVersion()) { + transactionService.runOnTransaction(new IOnTransaction() { + @Override + public Void execute() { + doTheSaving(); + return null; + } + }); + state.getScenarioInfo().afterCommit(); + fireAfterSave(); + notifyUserThatSavingIsDone(); + } + } + + private void fireAfterSave() { + for (IAfterSaveListener listener : listeners) { + listener.onAfterSave(); + } + } + + private void notifyUserThatSavingIsDone() { + try { + Messagebox.show(_("Scheduling saved"), _("Information"), + Messagebox.OK, Messagebox.INFORMATION); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void doTheSaving() { + state.getScenarioInfo().saveVersioningInfo(); + saveTasksToSave(); + removeTasksToRemove(); + saveAndDontPoseAsTransientOrderElements(); + subcontractedTaskDataDAO.removeOrphanedSubcontractedTaskData(); + } + + private void removeTasksToRemove() { + for (TaskElement taskElement : state.getToRemove()) { + if (taskElementDAO.exists(taskElement.getId())) { + // it might have already been saved in a previous save + // action + try { + taskElementDAO.remove(taskElement.getId()); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + } + } + } + + private void saveTasksToSave() { + for (TaskElement taskElement : state.getTasksToSave()) { + removeEmptyConsolidation(taskElement); + updateLimitingResourceQueueElementDates(taskElement); + taskElementDAO.save(taskElement); + if (taskElement.getTaskSource() != null + && taskElement.getTaskSource().isNewObject()) { + saveTaskSources(taskElement); + } + // Recursive iteration to put all the tasks of the + // gantt as transiet + dontPoseAsTransient(taskElement); + } + saveRootTaskIfNecessary(); + } + + private void saveRootTaskIfNecessary() { + if (!state.getTasksToSave().isEmpty()) { + TaskGroup rootTask = state.getRootTask(); + + updateRootTaskPosition(rootTask); + taskElementDAO.save(rootTask); + } + } + + private void updateRootTaskPosition(TaskGroup rootTask) { + final Date min = minDate(state.getTasksToSave()); + if (min != null) { + rootTask.setStartDate(min); + } + final Date max = maxDate(state.getTasksToSave()); + if (max != null) { + rootTask.setEndDate(max); + } + } + + private void saveTaskSources(TaskElement taskElement) { + taskSourceDAO.save(taskElement.getTaskSource()); + taskElement.getTaskSource().dontPoseAsTransientObjectAnymore(); + if (taskElement.isLeaf()) { + return; + } + for (TaskElement each : taskElement.getChildren()) { + saveTaskSources(each); + } + } + + private void updateLimitingResourceQueueElementDates( + TaskElement taskElement) { + if (taskElement.isLimiting()) { + Task task = (Task) taskElement; + updateLimitingResourceQueueElementDates(task); + } else if (!taskElement.isLeaf()) { + for (TaskElement each : taskElement.getChildren()) { + updateLimitingResourceQueueElementDates(each); + } + } + } + + private void updateLimitingResourceQueueElementDates(Task task) { + try { + LimitingResourceQueueElement limiting = task + .getAssociatedLimitingResourceQueueElementIfAny(); + + GanttDate earliestStart = resolveConstraints(task, Point.START); + GanttDate earliestEnd = resolveConstraints(task, Point.END); + + limiting.updateDates( + TaskElementAdapter.toIntraDay(earliestStart), + TaskElementAdapter.toIntraDay(earliestEnd)); + } catch (Exception e) { + // if this fails all the saving shouldn't fail + LOG.error( + "error updating associated LimitingResourceQueueElement for task: " + + task, e); + } + } + + private GanttDate resolveConstraints(Task task, Point point) { + List> dependencyConstraints = toConstraints( + adapter.getIncomingDependencies(task), point); + List> taskConstraints = getTaskConstraints(task); + + boolean dependenciesHavePriority = configuration + .isDependenciesConstraintsHavePriority(); + if (dependenciesHavePriority) { + return Constraint + . initialValue( + TaskElementAdapter.toGantt(getOrderInitDate())) + .withConstraints(taskConstraints) + .withConstraints(dependencyConstraints) + .applyWithoutFinalCheck(); + } else { + return Constraint + . initialValue( + TaskElementAdapter.toGantt(getOrderInitDate())) + .withConstraints(dependencyConstraints) + .withConstraints(taskConstraints) + .applyWithoutFinalCheck(); + } + } + + private List> getTaskConstraints(Task task) { + return TaskElementAdapter.getStartConstraintsFor(task, + getOrderInitDate()); + } + + private LocalDate getOrderInitDate() { + return LocalDate.fromDateFields(state.getRootTask() + .getOrderElement().getInitDate()); + } + + private List> toConstraints( + List> incomingDependencies, + Point point) { + List> result = new ArrayList>(); + for (DomainDependency each : incomingDependencies) { + result.addAll(constraintCalculator.getConstraints(each, point)); + } + return result; + } + + private void removeEmptyConsolidation(TaskElement taskElement) { + if ((taskElement.isLeaf()) && (!taskElement.isMilestone())) { + Consolidation consolidation = ((Task) taskElement) + .getConsolidation(); + if ((consolidation != null) + && (isEmptyConsolidation(consolidation))) { + if (!consolidation.isNewObject()) { + try { + consolidationDAO.remove(consolidation.getId()); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + } + ((Task) taskElement).setConsolidation(null); + } + } + } + + private boolean isEmptyConsolidation(final Consolidation consolidation) { + return transactionService + .runOnTransaction(new IOnTransaction() { + @Override + public Boolean execute() { + + consolidationDAO.reattach(consolidation); + if (consolidation instanceof CalculatedConsolidation) { + SortedSet consolidatedValues = ((CalculatedConsolidation) consolidation) + .getCalculatedConsolidatedValues(); + return consolidatedValues.isEmpty(); + } + if (consolidation instanceof NonCalculatedConsolidation) { + SortedSet consolidatedValues = ((NonCalculatedConsolidation) consolidation) + .getNonCalculatedConsolidatedValues(); + return consolidatedValues.isEmpty(); + } + return false; + + } + }); + } + + // newly added TaskElement such as milestones must be called + // dontPoseAsTransientObjectAnymore + private void dontPoseAsTransient(TaskElement taskElement) { + if (taskElement.isNewObject()) { + taskElement.dontPoseAsTransientObjectAnymore(); + } + dontPoseAsTransient(taskElement.getDependenciesWithThisOrigin()); + dontPoseAsTransient(taskElement + .getDependenciesWithThisDestination()); + Set> resourceAllocations = taskElement + .getAllResourceAllocations(); + dontPoseAsTransientAndChildrenObjects(resourceAllocations); + if (!taskElement.isLeaf()) { + for (TaskElement each : taskElement.getChildren()) { + dontPoseAsTransient(each); + } + } + if (taskElement instanceof Task) { + updateLimitingQueueDependencies((Task) taskElement); + dontPoseAsTransient(((Task) taskElement).getConsolidation()); + } + } + + private void dontPoseAsTransient( + Collection dependencies) { + for (Dependency each : dependencies) { + each.dontPoseAsTransientObjectAnymore(); + } + } + + private void updateLimitingQueueDependencies(Task t) { + + for (Dependency each : t.getDependenciesWithThisOrigin()) { + addLimitingDependencyIfNeeded(each); + removeLimitingDependencyIfNeeded(each); + } + } + + private void addLimitingDependencyIfNeeded(Dependency d) { + if (d.isDependencyBetweenLimitedAllocatedTasks() + && !d.hasLimitedQueueDependencyAssociated()) { + LimitingResourceQueueElement origin = calculateQueueElementFromDependency((Task) d + .getOrigin()); + LimitingResourceQueueElement destiny = calculateQueueElementFromDependency((Task) d + .getDestination()); + + LimitingResourceQueueDependency queueDependency = LimitingResourceQueueDependency + .create(origin, destiny, d, + toQueueDependencyType(d.getType())); + d.setQueueDependency(queueDependency); + limitingResourceQueueDependencyDAO.save(queueDependency); + queueDependency.dontPoseAsTransientObjectAnymore(); + } + } + + private LimitingResourceQueueElement calculateQueueElementFromDependency( + Task t) { + + LimitingResourceQueueElement result = null; + // TODO: Improve this method: One Task can only have one + // limiting resource allocation + Set> allocations = t + .getLimitingResourceAllocations(); + + if (allocations.isEmpty() || allocations.size() != 1) { + throw new ValidationException("Incorrect limiting resource " + + "allocation configuration"); + } + + for (ResourceAllocation r : allocations) { + result = r.getLimitingResourceQueueElement(); + } + + return result; + } + + private void removeLimitingDependencyIfNeeded(Dependency d) { + if (!d.isDependencyBetweenLimitedAllocatedTasks() + && (d.hasLimitedQueueDependencyAssociated())) { + LimitingResourceQueueDependency queueDependency = d + .getQueueDependency(); + queueDependency.getHasAsOrigin().remove(queueDependency); + queueDependency.getHasAsDestiny().remove(queueDependency); + d.setQueueDependency(null); + try { + limitingResourceQueueDependencyDAO.remove(queueDependency + .getId()); + } catch (InstanceNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Trying to delete instance " + + " does not exist"); + } + } + } + + private void dontPoseAsTransient(OrderElement orderElement) { + OrderElement order = (OrderElement) orderDAO + .loadOrderAvoidingProxyFor(orderElement); + order.dontPoseAsTransientObjectAnymore(); + dontPoseAsTransientAdvances(order.getDirectAdvanceAssignments()); + dontPoseAsTransientAdvances(order.getIndirectAdvanceAssignments()); + + for (OrderElement child : order.getAllChildren()) { + child.dontPoseAsTransientObjectAnymore(); + dontPoseAsTransientAdvances(child.getDirectAdvanceAssignments()); + dontPoseAsTransientAdvances(child + .getIndirectAdvanceAssignments()); + } + } + + private void dontPoseAsTransientAdvances( + Set advances) { + for (AdvanceAssignment advance : advances) { + advance.dontPoseAsTransientObjectAnymore(); + if (advance instanceof DirectAdvanceAssignment) { + dontPoseAsTransientMeasure(((DirectAdvanceAssignment) advance) + .getAdvanceMeasurements()); + } + } + } + + private void dontPoseAsTransientMeasure( + SortedSet list) { + for (AdvanceMeasurement measure : list) { + measure.dontPoseAsTransientObjectAnymore(); + } + } + + private void dontPoseAsTransient(Consolidation consolidation) { + if (consolidation != null) { + consolidation.dontPoseAsTransientObjectAnymore(); + if (consolidation.isCalculated()) { + dontPoseAsTransient(((CalculatedConsolidation) consolidation) + .getCalculatedConsolidatedValues()); + } else { + dontPoseAsTransient(((NonCalculatedConsolidation) consolidation) + .getNonCalculatedConsolidatedValues()); + } + } + } + + private void saveAndDontPoseAsTransientOrderElements() { + for (TaskElement taskElement : state.getTasksToSave()) { + if (taskElement.getOrderElement() != null) { + orderElementDAO.save(taskElement.getOrderElement()); + dontPoseAsTransient(taskElement.getOrderElement()); + } + } + } + + private void dontPoseAsTransient( + SortedSet values) { + for (ConsolidatedValue value : values) { + value.dontPoseAsTransientObjectAnymore(); + } + } + + private Date maxDate(Collection tasksToSave) { + List endDates = toEndDates(tasksToSave); + return endDates.isEmpty() ? null : Collections.max(endDates); + } + + private List toEndDates( + Collection tasksToSave) { + List result = new ArrayList(); + for (TaskElement taskElement : tasksToSave) { + Date endDate = taskElement.getEndDate(); + if (endDate != null) { + result.add(endDate); + } else { + LOG.warn("the task" + taskElement + " has null end date"); + } + } + return result; + } + + private Date minDate(Collection tasksToSave) { + List startDates = toStartDates(tasksToSave); + return startDates.isEmpty() ? null : Collections.min(startDates); + } + + private List toStartDates( + Collection tasksToSave) { + List result = new ArrayList(); + for (TaskElement taskElement : tasksToSave) { + Date startDate = taskElement.getStartDate(); + if (startDate != null) { + result.add(startDate); + } else { + LOG.warn("the task" + taskElement + " has null start date"); + } + } + return result; + } + + @Override + public String getName() { + return _("Save"); + } + + @Override + public void addListener(IAfterSaveListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(IAfterSaveListener listener) { + listeners.remove(listener); + } + + @Override + public String getImage() { + return "/common/img/ico_save.png"; + } + + private boolean userAcceptsCreateANewOrderVersion() { + try { + int status = Messagebox + .show(_("Confirm creating a new project version for this scenario and derived. Are you sure?"), + _("New project version"), Messagebox.OK + | Messagebox.CANCEL, + Messagebox.QUESTION); + return (Messagebox.OK == status); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + +}