Use a singleton for creating a SaveCommand

FEA: ItEr75S11PreventLooseChanges
This commit is contained in:
Óscar González Fernández 2011-09-06 18:27:43 +02:00
parent 4d79e2d212
commit f0721a8f23
5 changed files with 663 additions and 652 deletions

View file

@ -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();
}

View file

@ -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} <br />
* Contract for {@link SaveCommandBuilder} <br />
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public interface ISaveCommand extends ICommand<TaskElement> {
@ -36,10 +34,6 @@ public interface ISaveCommand extends ICommand<TaskElement> {
void onAfterSave();
}
public void setState(PlanningState planningState);
public void setConfiguration(PlannerConfiguration<TaskElement> configuration);
public void addListener(IAfterSaveListener listener);
public void removeListener(IAfterSaveListener listener);

View file

@ -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<TaskElement> configuration) {
saveCommand.setConfiguration(configuration);
saveCommand.setState(planningState);
return saveCommand;
return saveCommandBuilder.build(planningState, configuration);
}
private ICommand<TaskElement> buildReassigningCommand() {

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <br />
*
* 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 <ogonzalez@igalia.com>
* @author Javier Moran Rua <jmoran@igalia.com>
*/
@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<TaskElement> configuration;
private ConstraintCalculator<TaskElement> constraintCalculator;
@Autowired
private IAdHocTransactionService transactionService;
private List<IAfterSaveListener> listeners = new ArrayList<IAfterSaveListener>();
private IAdapterToTaskFundamentalProperties<TaskElement> adapter;
@Override
public void setState(PlanningState state) {
this.state = state;
}
@Override
public void setConfiguration(PlannerConfiguration<TaskElement> configuration) {
this.configuration = configuration;
this.adapter = configuration.getAdapter();
this.constraintCalculator = new ConstraintCalculator<TaskElement>(
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<TaskElement> context) {
if (state.getScenarioInfo().isUsingTheOwnerScenario()
|| userAcceptsCreateANewOrderVersion()) {
transactionService.runOnTransaction(new IOnTransaction<Void>() {
@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<Constraint<GanttDate>> dependencyConstraints = toConstraints(
adapter.getIncomingDependencies(task), point);
List<Constraint<GanttDate>> taskConstraints = getTaskConstraints(task);
boolean dependenciesHavePriority = configuration
.isDependenciesConstraintsHavePriority();
if (dependenciesHavePriority) {
return Constraint
.<GanttDate> initialValue(
TaskElementAdapter.toGantt(getOrderInitDate()))
.withConstraints(taskConstraints)
.withConstraints(dependencyConstraints)
.applyWithoutFinalCheck();
} else {
return Constraint
.<GanttDate> initialValue(
TaskElementAdapter.toGantt(getOrderInitDate()))
.withConstraints(dependencyConstraints)
.withConstraints(taskConstraints)
.applyWithoutFinalCheck();
}
}
private List<Constraint<GanttDate>> getTaskConstraints(Task task) {
return TaskElementAdapter.getStartConstraintsFor(task, getOrderInitDate());
}
private LocalDate getOrderInitDate() {
return LocalDate.fromDateFields(state.getRootTask().getOrderElement()
.getInitDate());
}
private List<Constraint<GanttDate>> toConstraints(
List<DomainDependency<TaskElement>> incomingDependencies,
Point point) {
List<Constraint<GanttDate>> result = new ArrayList<Constraint<GanttDate>>();
for (DomainDependency<TaskElement> 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<Boolean>() {
@Override
public Boolean execute() {
consolidationDAO.reattach(consolidation);
if (consolidation instanceof CalculatedConsolidation) {
SortedSet<CalculatedConsolidatedValue> consolidatedValues = ((CalculatedConsolidation) consolidation)
.getCalculatedConsolidatedValues();
return consolidatedValues.isEmpty();
}
if (consolidation instanceof NonCalculatedConsolidation) {
SortedSet<NonCalculatedConsolidatedValue> 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<ResourceAllocation<?>> 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<? extends Dependency> 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<ResourceAllocation<?>> 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<? extends AdvanceAssignment> advances) {
for (AdvanceAssignment advance : advances) {
advance.dontPoseAsTransientObjectAnymore();
if (advance instanceof DirectAdvanceAssignment) {
dontPoseAsTransientMeasure(((DirectAdvanceAssignment) advance)
.getAdvanceMeasurements());
}
}
}
private void dontPoseAsTransientMeasure(SortedSet<AdvanceMeasurement> 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<? extends ConsolidatedValue> values) {
for (ConsolidatedValue value : values) {
value.dontPoseAsTransientObjectAnymore();
}
}
public static void dontPoseAsTransientAndChildrenObjects(
Collection<? extends ResourceAllocation<?>> resourceAllocations) {
for (ResourceAllocation<?> each : resourceAllocations) {
each.dontPoseAsTransientObjectAnymore();
each.makeAssignmentsContainersDontPoseAsTransientAnyMore();
for (DayAssignment eachAssignment : each.getAssignments()) {
eachAssignment.dontPoseAsTransientObjectAnymore();
}
for (DerivedAllocation eachDerived : each.getDerivedAllocations()) {
eachDerived.dontPoseAsTransientObjectAnymore();
Collection<DerivedDayAssignmentsContainer> 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<? extends TaskElement> tasksToSave) {
List<Date> endDates = toEndDates(tasksToSave);
return endDates.isEmpty() ? null : Collections.max(endDates);
}
private List<Date> toEndDates(Collection<? extends TaskElement> tasksToSave) {
List<Date> result = new ArrayList<Date>();
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<? extends TaskElement> tasksToSave) {
List<Date> startDates = toStartDates(tasksToSave);
return startDates.isEmpty() ? null : Collections.min(startDates);
}
private List<Date> toStartDates(
Collection<? extends TaskElement> tasksToSave) {
List<Date> result = new ArrayList<Date>();
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);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <br />
*
* 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 <ogonzalez@igalia.com>
* @author Javier Moran Rua <jmoran@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SaveCommandBuilder {
private static final Log LOG = LogFactory.getLog(SaveCommandBuilder.class);
public ISaveCommand build(PlanningState planningState,
PlannerConfiguration<TaskElement> plannerConfiguration) {
SaveCommandImpl result = new SaveCommandImpl(planningState,
plannerConfiguration);
return ConcurrentModificationHandling.addHandling(
"/planner/index.zul;company_scheduling",
ISaveCommand.class, result);
}
public static void dontPoseAsTransientAndChildrenObjects(
Collection<? extends ResourceAllocation<?>> resourceAllocations) {
for (ResourceAllocation<?> each : resourceAllocations) {
each.dontPoseAsTransientObjectAnymore();
each.makeAssignmentsContainersDontPoseAsTransientAnyMore();
for (DayAssignment eachAssignment : each.getAssignments()) {
eachAssignment.dontPoseAsTransientObjectAnymore();
}
for (DerivedAllocation eachDerived : each
.getDerivedAllocations()) {
eachDerived.dontPoseAsTransientObjectAnymore();
Collection<DerivedDayAssignmentsContainer> 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<TaskElement> configuration;
private ConstraintCalculator<TaskElement> constraintCalculator;
private IAdapterToTaskFundamentalProperties<TaskElement> adapter;
private List<IAfterSaveListener> listeners = new ArrayList<IAfterSaveListener>();
public SaveCommandImpl(PlanningState planningState,
PlannerConfiguration<TaskElement> configuration) {
this.state = planningState;
this.configuration = configuration;
this.adapter = configuration.getAdapter();
this.constraintCalculator = new ConstraintCalculator<TaskElement>(
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<TaskElement> context) {
if (state.getScenarioInfo().isUsingTheOwnerScenario()
|| userAcceptsCreateANewOrderVersion()) {
transactionService.runOnTransaction(new IOnTransaction<Void>() {
@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<Constraint<GanttDate>> dependencyConstraints = toConstraints(
adapter.getIncomingDependencies(task), point);
List<Constraint<GanttDate>> taskConstraints = getTaskConstraints(task);
boolean dependenciesHavePriority = configuration
.isDependenciesConstraintsHavePriority();
if (dependenciesHavePriority) {
return Constraint
.<GanttDate> initialValue(
TaskElementAdapter.toGantt(getOrderInitDate()))
.withConstraints(taskConstraints)
.withConstraints(dependencyConstraints)
.applyWithoutFinalCheck();
} else {
return Constraint
.<GanttDate> initialValue(
TaskElementAdapter.toGantt(getOrderInitDate()))
.withConstraints(dependencyConstraints)
.withConstraints(taskConstraints)
.applyWithoutFinalCheck();
}
}
private List<Constraint<GanttDate>> getTaskConstraints(Task task) {
return TaskElementAdapter.getStartConstraintsFor(task,
getOrderInitDate());
}
private LocalDate getOrderInitDate() {
return LocalDate.fromDateFields(state.getRootTask()
.getOrderElement().getInitDate());
}
private List<Constraint<GanttDate>> toConstraints(
List<DomainDependency<TaskElement>> incomingDependencies,
Point point) {
List<Constraint<GanttDate>> result = new ArrayList<Constraint<GanttDate>>();
for (DomainDependency<TaskElement> 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<Boolean>() {
@Override
public Boolean execute() {
consolidationDAO.reattach(consolidation);
if (consolidation instanceof CalculatedConsolidation) {
SortedSet<CalculatedConsolidatedValue> consolidatedValues = ((CalculatedConsolidation) consolidation)
.getCalculatedConsolidatedValues();
return consolidatedValues.isEmpty();
}
if (consolidation instanceof NonCalculatedConsolidation) {
SortedSet<NonCalculatedConsolidatedValue> 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<ResourceAllocation<?>> 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<? extends Dependency> 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<ResourceAllocation<?>> 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<? extends AdvanceAssignment> advances) {
for (AdvanceAssignment advance : advances) {
advance.dontPoseAsTransientObjectAnymore();
if (advance instanceof DirectAdvanceAssignment) {
dontPoseAsTransientMeasure(((DirectAdvanceAssignment) advance)
.getAdvanceMeasurements());
}
}
}
private void dontPoseAsTransientMeasure(
SortedSet<AdvanceMeasurement> 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<? extends ConsolidatedValue> values) {
for (ConsolidatedValue value : values) {
value.dontPoseAsTransientObjectAnymore();
}
}
private Date maxDate(Collection<? extends TaskElement> tasksToSave) {
List<Date> endDates = toEndDates(tasksToSave);
return endDates.isEmpty() ? null : Collections.max(endDates);
}
private List<Date> toEndDates(
Collection<? extends TaskElement> tasksToSave) {
List<Date> result = new ArrayList<Date>();
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<? extends TaskElement> tasksToSave) {
List<Date> startDates = toStartDates(tasksToSave);
return startDates.isEmpty() ? null : Collections.min(startDates);
}
private List<Date> toStartDates(
Collection<? extends TaskElement> tasksToSave) {
List<Date> result = new ArrayList<Date>();
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);
}
}
}
}