From fe671aded5167cbf897965865d5dfa92a73b3ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacobo=20Aragunde=20P=C3=A9rez?= Date: Mon, 11 Oct 2010 19:55:45 +0200 Subject: [PATCH] Use DB snapshots the precalculate the data of the resource load graph in company screen. We prevent doing the calculation when the graph is shown. Now it's done when the application starts, and updated when one of the involved entities changes. FEA: ItEr62S03RFPerformanceCompanyView --- .../PredefinedDatabaseSnapshots.java | 45 ++++ .../entities/ResourceLoadChartData.java | 248 ++++++++++++++++++ .../planner/company/CompanyPlanningModel.java | 138 +--------- 3 files changed, 302 insertions(+), 129 deletions(-) create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceLoadChartData.java diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java index 387896d5d..8e37fb1e3 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java @@ -27,6 +27,10 @@ import java.util.concurrent.Callable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.navalplanner.business.calendars.entities.CalendarAvailability; +import org.navalplanner.business.calendars.entities.CalendarData; +import org.navalplanner.business.calendars.entities.CalendarException; import org.navalplanner.business.common.AdHocTransactionService; import org.navalplanner.business.common.IAdHocTransactionService; import org.navalplanner.business.costcategories.daos.ICostCategoryDAO; @@ -39,6 +43,13 @@ import org.navalplanner.business.labels.entities.Label; import org.navalplanner.business.labels.entities.LabelType; import org.navalplanner.business.orders.daos.IOrderDAO; import org.navalplanner.business.orders.entities.Order; +import org.navalplanner.business.planner.daos.IDayAssignmentDAO; +import org.navalplanner.business.planner.entities.DayAssignment; +import org.navalplanner.business.planner.entities.GenericResourceAllocation; +import org.navalplanner.business.planner.entities.ResourceAllocation; +import org.navalplanner.business.planner.entities.ResourceLoadChartData; +import org.navalplanner.business.planner.entities.SpecificResourceAllocation; +import org.navalplanner.business.planner.entities.TaskElement; import org.navalplanner.business.resources.daos.ICriterionDAO; import org.navalplanner.business.resources.daos.ICriterionTypeDAO; import org.navalplanner.business.resources.daos.IResourceDAO; @@ -49,6 +60,7 @@ import org.navalplanner.business.resources.entities.Machine; import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.VirtualWorker; import org.navalplanner.business.resources.entities.Worker; +import org.navalplanner.business.scenarios.IScenarioManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -125,6 +137,13 @@ public class PredefinedDatabaseSnapshots { return ordersCodes.getValue(); } + private IAutoUpdatedSnapshot + resourceLoadChartData; + + public ResourceLoadChartData snapshotResourceLoadChartData() { + return resourceLoadChartData.getValue(); + } + private boolean snapshotsRegistered = false; public void registerSnapshots() { @@ -153,6 +172,11 @@ public class PredefinedDatabaseSnapshots { calculateCustomerReferences(), Order.class); ordersCodes = snapshot("order codes", calculateOrdersCodes(), Order.class); + resourceLoadChartData = snapshot("resource load grouped by date", + calculateResourceLoadChartData(), + CalendarAvailability.class, CalendarException.class, + CalendarData.class, TaskElement.class, SpecificResourceAllocation.class, + GenericResourceAllocation.class, ResourceAllocation.class); } private IAutoUpdatedSnapshot snapshot(String name, @@ -311,4 +335,25 @@ public class PredefinedDatabaseSnapshots { }; } + @Autowired + private IDayAssignmentDAO dayAssignmentDAO; + + @Autowired + private IScenarioManager scenarioManager; + + private Callable calculateResourceLoadChartData() { + return new Callable() { + @Override + public ResourceLoadChartData call() + throws Exception { + + List dayAssignments = dayAssignmentDAO.getAllFor( + scenarioManager.getCurrent(), null, null); + List resources = resourceDAO.list(Resource.class); + return new ResourceLoadChartData(dayAssignments, resources); + + } + }; + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceLoadChartData.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceLoadChartData.java new file mode 100644 index 000000000..550049828 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceLoadChartData.java @@ -0,0 +1,248 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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.business.planner.entities; + +import static org.navalplanner.business.workingday.EffortDuration.min; +import static org.navalplanner.business.workingday.EffortDuration.zero; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.Map.Entry; + +import org.joda.time.LocalDate; +import org.navalplanner.business.calendars.entities.BaseCalendar; +import org.navalplanner.business.calendars.entities.ICalendar; +import org.navalplanner.business.calendars.entities.SameWorkHoursEveryDay; +import org.navalplanner.business.resources.entities.Resource; +import org.navalplanner.business.workingday.EffortDuration; +import org.navalplanner.business.workingday.IntraDayDate.PartialDay; + +/** + * This class groups the calculation of the three values needed for the + * chart of the company global resource load. The purpose of the class is + * having these data pre-calculated to prevent heavy algorithms being + * run each time the chart is shown. + * @see PredefinedDatabaseSnapshots + * @author Jacobo Aragunde Pérez + * + */ +public class ResourceLoadChartData { + + private SortedMap load; + + private SortedMap overload; + + private SortedMap availability; + + public ResourceLoadChartData(List dayAssignments, List resources) { + SortedMap> map = + groupDurationsByDayAndResource(dayAssignments); + this.load = calculateResourceLoadPerDate(map); + this.overload = calculateResourceOverloadPerDate(map); + if(load.keySet().isEmpty()) { + this.availability = new TreeMap(); + } + else { + this.availability = calculateAvailabilityDurationByDay( + resources, load.firstKey(), load.lastKey()); + } + + for (LocalDate day : this.overload.keySet()) { + EffortDuration overloadDuration = this.overload + .get(day); + EffortDuration maxDuration = this.availability.get(day); + this.overload.put(day, + overloadDuration.plus(maxDuration)); + } + } + + public SortedMap getLoad() { + return load; + } + + public SortedMap getOverload() { + return overload; + } + + public SortedMap getAvailability() { + return availability; + } + + private SortedMap> groupDurationsByDayAndResource( + List dayAssignments) { + SortedMap> map = + new TreeMap>(); + + for (DayAssignment dayAssignment : dayAssignments) { + final LocalDate day = dayAssignment.getDay(); + final EffortDuration dayAssignmentDuration = dayAssignment + .getDuration(); + Resource resource = dayAssignment.getResource(); + if (map.get(day) == null) { + map.put(day, new HashMap()); + } + Map forDay = map.get(day); + EffortDuration previousDuration = forDay.get(resource); + previousDuration = previousDuration != null ? previousDuration + : EffortDuration.zero(); + forDay.put(dayAssignment.getResource(), + previousDuration.plus(dayAssignmentDuration)); + } + return map; + } + + private SortedMap calculateResourceLoadPerDate( + SortedMap> durationsGrouped) { + SortedMap map = new TreeMap(); + + for (LocalDate date : durationsGrouped.keySet()) { + EffortDuration result = zero(); + PartialDay day = PartialDay.wholeDay(date); + for (Resource resource : durationsGrouped.get(date).keySet()) { + ICalendar calendar = resource.getCalendarOrDefault(); + EffortDuration workableTime = calendar.getCapacityOn(day); + EffortDuration assignedDuration = durationsGrouped.get( + day.getDate()).get(resource); + result = result.plus(min(assignedDuration, workableTime)); + } + + map.put(date, result); + } + return map; + } + + protected abstract class EffortByDayCalculator { + public SortedMap calculate( + Collection elements) { + SortedMap result = new TreeMap(); + if (elements.isEmpty()) { + return result; + } + for (T element : elements) { + if (included(element)) { + EffortDuration duration = getDurationFor(element); + LocalDate day = getDayFor(element); + EffortDuration previous = result.get(day); + previous = previous == null ? zero() : previous; + result.put(day, previous.plus(duration)); + } + } + return result; + } + + protected abstract LocalDate getDayFor(T element); + + protected abstract EffortDuration getDurationFor(T element); + + protected boolean included(T each) { + return true; + } + } + + private SortedMap calculateResourceOverloadPerDate( + SortedMap> dayAssignmentGrouped) { + return new EffortByDayCalculator>>() { + + @Override + protected LocalDate getDayFor( + Entry> element) { + return element.getKey(); + } + + @Override + protected EffortDuration getDurationFor( + Entry> element) { + EffortDuration result = zero(); + PartialDay day = PartialDay.wholeDay(element.getKey()); + for (Entry each : element.getValue() + .entrySet()) { + EffortDuration overlad = getOverloadAt(day, + each.getKey(), + each.getValue()); + result = result.plus(overlad); + } + return result; + } + + private EffortDuration getOverloadAt(PartialDay day, + Resource resource, EffortDuration assignedDuration) { + ICalendar calendar = resource.getCalendarOrDefault(); + EffortDuration workableDuration = calendar + .getCapacityOn(day); + if (assignedDuration.compareTo(workableDuration) > 0) { + return assignedDuration.minus(workableDuration); + } + return zero(); + } + }.calculate(dayAssignmentGrouped.entrySet()); + } + + private SortedMap calculateAvailabilityDurationByDay( + final List resources, LocalDate start, LocalDate finish) { + return new EffortByDayCalculator>>() { + + @Override + protected LocalDate getDayFor( + Entry> element) { + return element.getKey(); + } + + @Override + protected EffortDuration getDurationFor( + Entry> element) { + LocalDate day = element.getKey(); + return sumCalendarCapacitiesForDay(resources, day); + } + + }.calculate(getResourcesByDateBetween(resources, start, finish)); + } + + protected static EffortDuration sumCalendarCapacitiesForDay( + Collection resources, LocalDate day) { + PartialDay wholeDay = PartialDay.wholeDay(day); + EffortDuration sum = zero(); + for (Resource resource : resources) { + sum = sum.plus(calendarCapacityFor(resource, + wholeDay)); + } + return sum; + } + + protected static EffortDuration calendarCapacityFor(Resource resource, + PartialDay day) { + return resource.getCalendarOrDefault().getCapacityOn(day); + } + + private Set>> getResourcesByDateBetween( + List resources, LocalDate start, LocalDate finish) { + Map> result = new HashMap>(); + for (LocalDate date = new LocalDate(start); date.compareTo(finish) <= 0; date = date + .plusDays(1)) { + result.put(date, resources); + } + return result.entrySet(); + } +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java index 02765449d..37963b32c 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java @@ -20,8 +20,6 @@ package org.navalplanner.web.planner.company; -import static org.navalplanner.business.workingday.EffortDuration.min; -import static org.navalplanner.business.workingday.EffortDuration.zero; import static org.navalplanner.web.I18nHelper._; import static org.navalplanner.web.resourceload.ResourceLoadModel.asDate; @@ -36,30 +34,25 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.joda.time.LocalDate; -import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.common.IAdHocTransactionService; import org.navalplanner.business.common.IOnTransaction; import org.navalplanner.business.common.daos.IConfigurationDAO; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.hibernate.notification.PredefinedDatabaseSnapshots; import org.navalplanner.business.orders.daos.IOrderDAO; import org.navalplanner.business.orders.entities.Order; import org.navalplanner.business.orders.entities.OrderStatusEnum; -import org.navalplanner.business.planner.daos.IDayAssignmentDAO; import org.navalplanner.business.planner.daos.ITaskElementDAO; -import org.navalplanner.business.planner.entities.DayAssignment; import org.navalplanner.business.planner.entities.ICostCalculator; 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.TaskMilestone; -import org.navalplanner.business.resources.daos.IResourceDAO; -import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.scenarios.IScenarioManager; import org.navalplanner.business.scenarios.entities.Scenario; import org.navalplanner.business.templates.entities.OrderTemplate; @@ -140,12 +133,6 @@ public abstract class CompanyPlanningModel implements ICompanyPlanningModel { @Autowired private IOrderDAO orderDAO; - @Autowired - private IResourceDAO resourceDAO; - - @Autowired - private IDayAssignmentDAO dayAssignmentDAO; - @Autowired private ITaskElementDAO taskElementDAO; @@ -177,6 +164,9 @@ public abstract class CompanyPlanningModel implements ICompanyPlanningModel { private Scenario currentScenario; + @Autowired + private PredefinedDatabaseSnapshots databaseSnapshots; + private LocalDate filterStartDate; private LocalDate filterFinishDate; private static final EnumSet STATUS_VISUALIZED = EnumSet @@ -817,129 +807,19 @@ public abstract class CompanyPlanningModel implements ICompanyPlanningModel { private SortedMap getLoad(LocalDate start, LocalDate finish) { - List dayAssignments = dayAssignmentDAO.getAllFor( - currentScenario, start, finish); - SortedMap> durationsGrouped = groupDurationsByDayAndResource(dayAssignments); - return calculateHoursAdditionByDayWithoutOverload(durationsGrouped); + return groupAsNeededByZoom(databaseSnapshots. + snapshotResourceLoadChartData().getLoad().subMap(start, finish)); } private SortedMap getOverload( LocalDate start, LocalDate finish) { - List dayAssignments = dayAssignmentDAO.getAllFor( - currentScenario, start, finish); - - SortedMap> dayAssignmentGrouped = groupDurationsByDayAndResource(dayAssignments); - SortedMap mapDayAssignments = calculateHoursAdditionByDayJustOverload(dayAssignmentGrouped); - SortedMap mapMaxAvailability = calculateAvailabilityDurationByDay( - resourceDAO.list(Resource.class), start, finish); - - for (LocalDate day : mapDayAssignments.keySet()) { - if (day.compareTo(new LocalDate(start)) >= 0 - && day.compareTo(new LocalDate(finish)) <= 0) { - EffortDuration overloadDuration = mapDayAssignments - .get(day); - EffortDuration maxDuration = mapMaxAvailability.get(day); - mapDayAssignments.put(day, - overloadDuration.plus(maxDuration)); - } - } - - return mapDayAssignments; - } - - private SortedMap calculateHoursAdditionByDayWithoutOverload( - SortedMap> durationsGrouped) { - SortedMap map = new TreeMap(); - - for (LocalDate date : durationsGrouped.keySet()) { - EffortDuration result = zero(); - PartialDay day = PartialDay.wholeDay(date); - for (Resource resource : durationsGrouped.get(date).keySet()) { - ICalendar calendar = resource.getCalendarOrDefault(); - EffortDuration workableTime = calendar.getCapacityOn(day); - EffortDuration assignedDuration = durationsGrouped.get( - day.getDate()).get(resource); - result = result.plus(min(assignedDuration, workableTime)); - } - - map.put(date, result); - } - return groupAsNeededByZoom(map); - } - - private SortedMap calculateHoursAdditionByDayJustOverload( - SortedMap> dayAssignmentGrouped) { - return new EffortByDayCalculator>>() { - - @Override - protected LocalDate getDayFor( - Entry> element) { - return element.getKey(); - } - - @Override - protected EffortDuration getDurationFor( - Entry> element) { - EffortDuration result = zero(); - PartialDay day = PartialDay.wholeDay(element.getKey()); - for (Entry each : element.getValue() - .entrySet()) { - EffortDuration overlad = getOverloadAt(day, - each.getKey(), - each.getValue()); - result = result.plus(overlad); - } - return result; - } - - private EffortDuration getOverloadAt(PartialDay day, - Resource resource, EffortDuration assignedDuration) { - ICalendar calendar = resource.getCalendarOrDefault(); - EffortDuration workableDuration = calendar - .getCapacityOn(day); - if (assignedDuration.compareTo(workableDuration) > 0) { - return assignedDuration.minus(workableDuration); - } - return zero(); - } - }.calculate(dayAssignmentGrouped.entrySet()); + return groupAsNeededByZoom( + databaseSnapshots.snapshotResourceLoadChartData().getOverload().subMap(start, finish)); } private SortedMap getCalendarMaximumAvailability( LocalDate start, LocalDate finish) { - return calculateAvailabilityDurationByDay( - resourceDAO.list(Resource.class), start, finish); - } - - private SortedMap calculateAvailabilityDurationByDay( - List resources, LocalDate start, LocalDate finish) { - return new EffortByDayCalculator>>() { - - @Override - protected LocalDate getDayFor( - Entry> element) { - return element.getKey(); - } - - @Override - protected EffortDuration getDurationFor( - Entry> element) { - LocalDate day = element.getKey(); - List resources = element.getValue(); - return sumCalendarCapacitiesForDay(resources, day); - } - - }.calculate(getResourcesByDateBetween(resources, start, finish)); - } - - private Set>> getResourcesByDateBetween( - List resources, LocalDate start, LocalDate finish) { - Map> result = new HashMap>(); - for (LocalDate date = new LocalDate(start); date.compareTo(finish) <= 0; date = date - .plusDays(1)) { - result.put(date, resources); - } - return result.entrySet(); + return databaseSnapshots.snapshotResourceLoadChartData().getAvailability().subMap(start, finish); } }