/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.business.resources.entities; import static org.libreplan.business.common.exceptions.ValidationException.invalidValue; import static org.libreplan.business.workingday.EffortDuration.zero; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import javax.validation.Valid; import javax.validation.constraints.AssertFalse; import javax.validation.constraints.AssertTrue; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.joda.time.LocalDate; import org.libreplan.business.calendars.entities.AvailabilityTimeLine; import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.calendars.entities.ICalendar; import org.libreplan.business.calendars.entities.ResourceCalendar; import org.libreplan.business.calendars.entities.SameWorkHoursEveryDay; import org.libreplan.business.common.BaseEntity; import org.libreplan.business.common.IHumanIdentifiable; import org.libreplan.business.common.IOnTransaction; import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.Registry; import org.libreplan.business.common.entities.Configuration; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.common.exceptions.MultipleInstancesException; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.costcategories.entities.CostCategory; import org.libreplan.business.costcategories.entities.ResourcesCostCategoryAssignment; import org.libreplan.business.planner.entities.AvailabilityCalculator; import org.libreplan.business.planner.entities.DayAssignment; import org.libreplan.business.planner.entities.ResourceAllocation; import org.libreplan.business.resources.daos.IResourceDAO; import org.libreplan.business.scenarios.entities.Scenario; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.EffortDuration.IEffortFrom; import org.libreplan.business.workingday.IntraDayDate; import org.libreplan.business.workingday.IntraDayDate.PartialDay; /** * This class acts as the base class for all resources. * * @author Fernando Bellas Permuy <fbellas@udc.es> * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> * @author Jacobo Aragunde Perez <jaragunde@igalia.com> */ public abstract class Resource extends IntegrationEntity implements IHumanIdentifiable, Comparable<Resource> { public static class AllResourceAssignments implements IAssignmentsOnResourceCalculator { @Override public List<DayAssignment> getAssignments(Resource resource) { return resource.getAssignments(); } } public static List<Machine> machines(Collection<? extends Resource> resources) { return filter(Machine.class, resources); } public static List<Worker> workers(Collection<? extends Resource> resources) { return filter(Worker.class, resources); } public static <T extends Resource> List<T> filter(Class<T> klass, Collection<? extends Resource> resources) { List<T> result = new ArrayList<T>(); for (Resource each : resources) { if ( klass.isInstance(each) ) { result.add(klass.cast(each)); } } return result; } public static List<Resource> sortByName(List<Resource> resources) { Collections.sort(resources, new Comparator<Resource>() { @Override public int compare(Resource o1, Resource o2) { return String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName()); } }); return resources; } public static String getCaptionFor(ResourceAllocation<?> resourceAllocation) { return getCaptionFor(resourceAllocation.getAssociatedResources()); } public static String getCaptionFor(List<Resource> resources) { List<String> values = new ArrayList<String>(); for (Resource each: resources) { values.add(each.getShortDescription()); } return StringUtils.join(values, ", "); } private ResourceCalendar calendar; private Set<CriterionSatisfaction> criterionSatisfactions = new HashSet<CriterionSatisfaction>(); private Set<DayAssignment> dayAssignments = new HashSet<DayAssignment>(); private Map<LocalDate, List<DayAssignment>> assignmentsByDayCached = null; private Set<ResourcesCostCategoryAssignment> resourcesCostCategoryAssignments = new HashSet<ResourcesCostCategoryAssignment>(); private ResourceType resourceType = ResourceType.NON_LIMITING_RESOURCE; private LimitingResourceQueue limitingResourceQueue; private void clearCachedData() { assignmentsByDayCached = null; dayAssignmentsState.clearCachedData(); } private List<DayAssignment> getAssignmentsForDay(LocalDate date) { if ( assignmentsByDayCached == null ) { assignmentsByDayCached = DayAssignment.byDay(getAssignments()); } List<DayAssignment> list = assignmentsByDayCached.get(date); if ( list == null ){ return Collections.emptyList(); } return list; } private abstract class DayAssignmentsState { private List<DayAssignment> cachedAssignments; abstract List<DayAssignment> calculateAssignments(); List<DayAssignment> getAssignments() { if ( cachedAssignments != null ) { return cachedAssignments; } return cachedAssignments = calculateAssignments(); } void clearCachedData() { cachedAssignments = null; } } private class UsingScenarioManager extends DayAssignmentsState { @Override List<DayAssignment> calculateAssignments() { List<DayAssignment> result = new ArrayList<DayAssignment>(); Scenario current = Registry.getScenarioManager().getCurrent(); for (DayAssignment each : dayAssignments) { if ( each.getScenario() != null && each.getScenario().equals(current) ) { result.add(each); } } return result; } } private class OnSpecifiedScenario extends DayAssignmentsState { private final Scenario currentScenario; private OnSpecifiedScenario(Scenario currentScenario) { Validate.notNull(currentScenario); this.currentScenario = currentScenario; } @Override List<DayAssignment> calculateAssignments() { List<DayAssignment> result = new ArrayList<DayAssignment>(); for (DayAssignment each : dayAssignments) { if ( isTransient(each) || each.getScenario().equals(currentScenario) ) { result.add(each); } } return result; } private boolean isTransient(DayAssignment each) { return each.getScenario() == null; } } private DayAssignmentsState dayAssignmentsState = new UsingScenarioManager(); @Valid public Set<CriterionSatisfaction> getCriterionSatisfactions() { Set<CriterionSatisfaction> satisfactionActives = new HashSet<CriterionSatisfaction>(); for(CriterionSatisfaction satisfaction:criterionSatisfactions){ if( !satisfaction.isIsDeleted() ) { satisfactionActives.add(satisfaction); } } return satisfactionActives; } public CriterionSatisfaction getCriterionSatisfactionByCode(String code) throws InstanceNotFoundException { if ( StringUtils.isBlank(code) ) { throw new InstanceNotFoundException(code, CriterionSatisfaction.class.getName()); } for (CriterionSatisfaction i : criterionSatisfactions) { if ( i.getCode().equalsIgnoreCase(StringUtils.trim(code)) ) { return i; } } throw new InstanceNotFoundException(code, CriterionSatisfaction.class.getName()); } public abstract String getShortDescription(); public abstract String getName(); private interface IPredicate { public boolean accepts(CriterionSatisfaction satisfaction); } public class Query { private List<IPredicate> predicates = new ArrayList<IPredicate>(); private Query() { } public Query from(final ICriterionType<?> type) { return withNewPredicate(new IPredicate() { @Override public boolean accepts(CriterionSatisfaction satisfaction) { return type.contains(satisfaction.getCriterion()); } }); } private Query withNewPredicate(IPredicate newPredicate) { predicates.add(newPredicate); return this; } public Query at(LocalDate date) { return enforcedInAll(Interval.point(date)); } public Query between(LocalDate start, LocalDate end) { return enforcedInAll(Interval.range(start, end)); } public Query enforcedInAll(final Interval interval) { return withNewPredicate(new IPredicate() { @Override public boolean accepts(CriterionSatisfaction satisfaction) { return satisfaction.isAlwaysEnforcedIn(interval); } }); } public Query overlapsWith(final Interval interval) { return withNewPredicate(new IPredicate() { @Override public boolean accepts(CriterionSatisfaction satisfaction) { return satisfaction.overlapsWith(interval); } }); } public Query from(final Criterion criterion) { return withNewPredicate(new IPredicate() { @Override public boolean accepts(CriterionSatisfaction satisfaction) { return criterion.includes(satisfaction.getCriterion()); } }); } public Query exactly(final Criterion criterion) { return withNewPredicate(new IPredicate() { @Override public boolean accepts(CriterionSatisfaction satisfaction) { return criterion.isEquivalent(satisfaction.getCriterion()); } }); } /** * Method called to retrieve the result. If no predicate was set, it * returns all satisfactions * @return the satisfactions matched by all predicates specified ordered * by start date. */ public List<CriterionSatisfaction> result() { ArrayList<CriterionSatisfaction> result = new ArrayList<CriterionSatisfaction>(); for (CriterionSatisfaction criterionSatisfaction : getCriterionSatisfactions()) { if (isAcceptedByAllPredicates(criterionSatisfaction)) { result.add(criterionSatisfaction); } } Collections.sort(result, CriterionSatisfaction.BY_START_COMPARATOR); return result; } public List<CriterionSatisfaction> result(Set<CriterionSatisfaction> list) { ArrayList<CriterionSatisfaction> result = new ArrayList<CriterionSatisfaction>(); for (CriterionSatisfaction criterionSatisfaction : list) { if (isAcceptedByAllPredicates(criterionSatisfaction)) { result.add(criterionSatisfaction); } } Collections.sort(result, CriterionSatisfaction.BY_START_COMPARATOR); return result; } private boolean isAcceptedByAllPredicates( CriterionSatisfaction criterionSatisfaction) { for (IPredicate predicate : predicates) { if (!predicate.accepts(criterionSatisfaction)) { return false; } } return true; } public Query current() { return withNewPredicate(new IPredicate() { @Override public boolean accepts(CriterionSatisfaction satisfaction) { return satisfaction.isCurrent(); } }); } public List<Criterion> asCriterions() { LinkedHashSet<Criterion> result = new LinkedHashSet<Criterion>(); for (CriterionSatisfaction criterionSatisfaction : result()) { result.add(criterionSatisfaction.getCriterion()); } return new ArrayList<Criterion>(result); } public Query oneOf(ICriterionType<?>[] laboralRelatedTypes) { return oneOf(Arrays.asList(laboralRelatedTypes)); } public Query oneOf(final Collection<? extends ICriterionType<?>> types) { return withNewPredicate(new IPredicate() { @Override public boolean accepts(CriterionSatisfaction satisfaction) { for (ICriterionType<?> criterionType : types) { if (criterionType.contains(satisfaction.getCriterion())) { return true; } } return false; } }); } } public Query query() { return new Query(); } public Set<CriterionSatisfaction> getAllSatisfactions() { return new HashSet<CriterionSatisfaction>(criterionSatisfactions); } public Collection<CriterionSatisfaction> getSatisfactionsFor( ICriterionType<?> type) { return query().from(type).result(); } public List<CriterionSatisfaction> getSatisfactionsFor(Criterion criterion) { return query().from(criterion).result(); } public List<Criterion> getCurrentCriterionsFor(ICriterionType<?> type) { return query().from(type).current().asCriterions(); } public Collection<CriterionSatisfaction> getCurrentSatisfactionsFor( ICriterionType<?> criterionType) { return query().from(criterionType).current().result(); } public List<CriterionSatisfaction> getCurrentSatisfactionsFor( Criterion criterion) { return query().from(criterion).current().result(); } public CriterionSatisfaction addSatisfaction( CriterionWithItsType criterionWithItsType) { LocalDate today = new LocalDate(); return addSatisfaction(criterionWithItsType, Interval.from(today)); } private static class EnsureSatisfactionIsCorrect { private EnsureSatisfactionIsCorrect(Resource resource, ICriterionType<?> type, CriterionSatisfaction satisfaction) { Validate.notNull(resource); Validate.notNull(satisfaction.getResource()); Validate.notNull(satisfaction); if (!satisfaction.getResource().equals(resource)) { throw new IllegalArgumentException( "the satisfaction is not related to this resource"); } this.type = new CriterionWithItsType(type, satisfaction .getCriterion()); this.interval = satisfaction.getInterval(); this.resource = resource; } private final Resource resource; private final CriterionWithItsType type; private final Interval interval; CriterionSatisfaction addSatisfaction() { return resource.addSatisfaction(type, interval); } boolean canAddSatisfaction() { return resource.canAddSatisfaction(type, interval); } } public CriterionSatisfaction addSatisfaction(ICriterionType<?> type, CriterionSatisfaction satisfaction) { return new EnsureSatisfactionIsCorrect(this, type, satisfaction) .addSatisfaction(); } public CriterionSatisfaction addSatisfaction( CriterionWithItsType criterionWithItsType, Interval interval){ Criterion criterion = criterionWithItsType.getCriterion(); ICriterionType<?> type = criterionWithItsType.getType(); CriterionSatisfaction newSatisfaction = createNewSatisfaction(interval, criterion); if (canAddSatisfaction(criterionWithItsType, interval)) { newSatisfaction.validate(); criterionSatisfactions.add(newSatisfaction); return newSatisfaction; } final String message = getReasonForNotAddingSatisfaction(type); throw new IllegalStateException(message); } private String getReasonForNotAddingSatisfaction(ICriterionType<?> type) { if (cannotApplyResourceToCriterionType(type)) { return "Cannot apply criterion of type " + type.getName() + " to a " + getClass().getSimpleName(); } else { return "Criterion satisfaction overlaps with other criterion satisfactions"; } } private boolean cannotApplyResourceToCriterionType(ICriterionType<?> type) { return (type != null && !type.criterionCanBeRelatedTo(getClass())); } private CriterionSatisfaction createNewSatisfaction(Interval interval, Criterion criterion) { CriterionSatisfaction newSatisfaction = CriterionSatisfaction.create(criterion, this, interval); return newSatisfaction; } /** * @param orderedSatisfactions * @param newSatisfaction * @return the position in which if newSatisfaction is inserted would comply * with the following: * <ul> * <li>newSatisfaction startDate would be equal or posterior to all * the previous satisfactions</li> * <li>newSatisfaction startDate would be previous to all the * posterior satisfactions</li> * </ul> */ private int findPlace(List<CriterionSatisfaction> orderedSatisfactions, CriterionSatisfaction newSatisfaction) { int position = Collections.binarySearch(orderedSatisfactions, newSatisfaction, CriterionSatisfaction.BY_START_COMPARATOR); if (position >= 0) { return position + 1; } else { return Math.abs(position) - 1; } } public List<CriterionSatisfaction> finish( CriterionWithItsType criterionWithItsType) { LocalDate today = new LocalDate(); return finishEnforcedAt(criterionWithItsType.getCriterion(), today); } public List<CriterionSatisfaction> finishEnforcedAt(Criterion criterion, LocalDate date) { ArrayList<CriterionSatisfaction> result = new ArrayList<CriterionSatisfaction>(); for (CriterionSatisfaction criterionSatisfaction : query() .exactly(criterion).at(date).result()) { criterionSatisfaction.finish(date); result.add(criterionSatisfaction); } return result; } public void modifySatisfaction(CriterionSatisfaction original, Interval interval){ /* Create a temporal criterion satisfaction. */ CriterionType type = original.getCriterion().getType(); CriterionSatisfaction temporal = createNewSatisfaction(interval, original.getCriterion()); temporal.setResource(this); boolean canAdd=false; if (contains(original)) { try{ removeCriterionSatisfaction(original); canAdd = canAddSatisfaction(type, temporal); if(canAdd){ //update original original.setStartDate(interval.getStart()); original.finish(interval.getEnd()); } original.validate(); criterionSatisfactions.add(original); if(!canAdd){ throw new IllegalStateException( "This interval "+original.getCriterion().getName()+" not is valid because exists overlap with other criterion satisfaction"); } }catch(IllegalArgumentException e){ throw new IllegalArgumentException (original.getCriterion().getName()+" : "+e.getMessage()); } }else{ throw new IllegalStateException( "The criterion satisfaction "+original.getCriterion().getName()+" not is activated for this resource"); } } public boolean canAddSatisfaction( CriterionWithItsType criterionWithItsType, Interval interval) { CriterionSatisfaction satisfaction = createNewSatisfaction(interval, criterionWithItsType.getCriterion()); return canAddSatisfaction(criterionWithItsType.getType(), satisfaction, this.getCriterionSatisfactions()); } private boolean canAddSatisfaction(ICriterionType<?> type, CriterionSatisfaction satisfaction, Set<CriterionSatisfaction> satisfactions) { final Criterion criterion = satisfaction.getCriterion(); final Interval interval = Interval.range(satisfaction.getStartDate(), satisfaction.getEndDate()); if (!type.criterionCanBeRelatedTo(getClass())) { return false; } CriterionSatisfaction previousSameCriterion = getPreviousSameCriterion (criterion, satisfaction, satisfactions); CriterionSatisfaction posteriorSameCriterion = getNextSameCriterion (criterion, satisfaction, satisfactions); boolean canAdd = ((previousSameCriterion == null || !previousSameCriterion.overlapsWith(interval)) && ( posteriorSameCriterion == null || !posteriorSameCriterion.overlapsWith(interval))); if(!canAdd) { return false; } if (type.isAllowSimultaneousCriterionsPerResource()){ return true; } CriterionSatisfaction previous = getPrevious(type , satisfaction, satisfactions); CriterionSatisfaction posterior = getNext(type, satisfaction, satisfactions); return (previous == null || !previous.overlapsWith(interval)) && ( posterior == null || !posterior.overlapsWith(interval)); } public boolean _canAddSatisfaction( CriterionWithItsType criterionWithItsType, Interval interval) { ICriterionType<?> type = criterionWithItsType.getType(); Criterion criterion = criterionWithItsType.getCriterion(); if (!type.criterionCanBeRelatedTo(getClass())) { return false; } CriterionSatisfaction newSatisfaction = createNewSatisfaction(interval, criterion); CriterionSatisfaction previousSameCriterion = getPreviousSameCriterion (criterion, newSatisfaction,this.getCriterionSatisfactions()); CriterionSatisfaction posteriorSameCriterion = getNextSameCriterion (criterion, newSatisfaction,this.getCriterionSatisfactions()); boolean canAdd = ((previousSameCriterion == null || !previousSameCriterion.overlapsWith(interval)) && ( posteriorSameCriterion == null || !posteriorSameCriterion.overlapsWith(interval))); if(!canAdd) { return false; } if (type.isAllowSimultaneousCriterionsPerResource()){ return true; } CriterionSatisfaction previous = getPrevious(criterionWithItsType .getType(), newSatisfaction,this.getCriterionSatisfactions()); CriterionSatisfaction posterior = getNext(criterionWithItsType .getType(), newSatisfaction,this.getCriterionSatisfactions()); return (previous == null || !previous.overlapsWith(interval)) && ( posterior == null || !posterior.overlapsWith(interval)); } public boolean canAddSatisfaction(ICriterionType<?> type, CriterionSatisfaction satisfaction) { EnsureSatisfactionIsCorrect ensureSatisfactionIsCorrect = new EnsureSatisfactionIsCorrect( this, type, satisfaction); return ensureSatisfactionIsCorrect.canAddSatisfaction(); } private CriterionSatisfaction getNext(ICriterionType<?> type, CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) { List<CriterionSatisfaction> ordered = query().from(type).result(list); int position = findPlace(ordered, newSatisfaction); CriterionSatisfaction next = position != ordered.size() ? ordered .get(position) : null; return next; } private CriterionSatisfaction getPrevious(ICriterionType<?> type, CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) { List<CriterionSatisfaction> ordered = query().from(type).result(list); int position = findPlace(ordered, newSatisfaction); CriterionSatisfaction previous = position > 0 ? ordered .get(position - 1) : null; return previous; } private CriterionSatisfaction getNextSameCriterion(Criterion criterion, CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) { List<CriterionSatisfaction> ordered = query().from(criterion).result(list); int position = findPlace(ordered, newSatisfaction); CriterionSatisfaction next = position != ordered.size() ? ordered .get(position) : null; return next; } private CriterionSatisfaction getPreviousSameCriterion(Criterion criterion, CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) { List<CriterionSatisfaction> ordered = query().from(criterion).result(list); int position = findPlace(ordered, newSatisfaction); CriterionSatisfaction previous = position > 0 ? ordered .get(position - 1) : null; return previous; } public void removeCriterionSatisfaction(CriterionSatisfaction satisfaction) { criterionSatisfactions.remove(satisfaction); } public boolean contains(CriterionSatisfaction satisfaction) { return criterionSatisfactions.contains(satisfaction); } /** * @throws IllegalArgumentException in case of overlapping */ public void checkNotOverlaps() { checkNotOverlaps(getRelatedTypes()); } private List<CriterionType> getRelatedTypes() { List<CriterionType> types = new ArrayList<CriterionType>(); for (CriterionSatisfaction criterionSatisfaction : this.getCriterionSatisfactions()) { types.add(criterionSatisfaction.getCriterion().getType()); } return types; } /** * @throws IllegalArgumentException in case of overlapping */ private void checkNotOverlaps(List<CriterionType> types) { for (CriterionType criterionType : types) { List<CriterionSatisfaction> satisfactions = query().from( criterionType).result(); ListIterator<CriterionSatisfaction> listIterator = satisfactions .listIterator(); while (listIterator.hasNext()) { CriterionSatisfaction current = listIterator.next(); CriterionSatisfaction previous = getPrevious(listIterator); CriterionSatisfaction next = getNext(listIterator); if (previous != null) { checkNotOverlaps(previous, current); } if (next != null) { checkNotOverlaps(current, next); } } } } /** * IMPORTANT: <code>before</code> and <code>after</code> must refer to the * same <code>CriterionType</code> * * @throws IllegalArgumentException in case of overlapping */ private void checkNotOverlaps(CriterionSatisfaction before, CriterionSatisfaction after) { CriterionType criterionType = before.getCriterion().getType(); /* * If criterion satisfactions refer to the same Criterion, they must not * overlap (regardless of its CriterionType allows simultaneous * criterion satisfactions per resource). */ if (before.getCriterion().equals(after.getCriterion()) && !before.goesBeforeWithoutOverlapping(after)) { throw new IllegalArgumentException(createOverlapsMessage(before, after)); } /* * If CriterionType does not allow simultaneous criterion satisfactions * per resource, criterion satisfactions must not overlap (regardless * of they refer to different Criterion objects). */ if (!criterionType.isAllowSimultaneousCriterionsPerResource() && !before.goesBeforeWithoutOverlapping(after)) { throw new IllegalArgumentException(createOverlapsMessage(before, after)); } } private String createOverlapsMessage(CriterionSatisfaction before, CriterionSatisfaction after) { return new StringBuilder("the satisfaction").append(before).append( "overlaps with").append(after).toString(); } private CriterionSatisfaction getNext( ListIterator<CriterionSatisfaction> listIterator) { if (listIterator.hasNext()) { CriterionSatisfaction result = listIterator.next(); listIterator.previous(); return result; } return null; } private CriterionSatisfaction getPrevious( ListIterator<CriterionSatisfaction> listIterator) { listIterator.previous(); try { if (listIterator.hasPrevious()) { CriterionSatisfaction result = listIterator.previous(); listIterator.next(); return result; } return null; } finally { listIterator.next(); } } public void setCalendar(ResourceCalendar calendar) { this.calendar = calendar; if (calendar != null) { calendar.setResource(this); } } public ResourceCalendar getCalendar() { return calendar; } public void setResourceCalendar(String calendarCode) throws InstanceNotFoundException, MultipleInstancesException { ResourceCalendar calendar; if (StringUtils.isBlank(calendarCode)) { calendar = Registry.getConfigurationDAO().getConfiguration(). getDefaultCalendar().newDerivedResourceCalendar(); } else { BaseCalendar baseCalendar = Registry.getBaseCalendarDAO() .findByCode(calendarCode); calendar = baseCalendar.newDerivedResourceCalendar(); } setCalendar(calendar); } public EffortDuration getAssignedEffort(LocalDate localDate) { return DayAssignment.sum(getAssignmentsForDay(localDate)); } public EffortDuration getAssignedDurationDiscounting( Map<Long, Set<BaseEntity>> allocationsFromWhichDiscountHours, LocalDate day) { EffortDuration result = zero(); for (DayAssignment dayAssignment : getAssignmentsForDay(day)) { if ( !dayAssignment.belongsToSomeOf(allocationsFromWhichDiscountHours) ) { result = result.plus(dayAssignment.getDuration()); } } return result; } public void addNewAssignments(Collection<? extends DayAssignment> assignments) { Validate.notNull(assignments); Validate.noNullElements(assignments); clearCachedData(); this.dayAssignments.addAll(assignments); } public void removeAssignments(Collection<? extends DayAssignment> assignments) { Validate.noNullElements(assignments); clearCachedData(); this.dayAssignments.removeAll(assignments); } public List<DayAssignment> getAssignments() { return dayAssignmentsState.getAssignments(); } public void useScenario(Scenario scenario) { dayAssignmentsState = new OnSpecifiedScenario(scenario); } public int getTotalWorkHours(LocalDate start, LocalDate end) { return getTotalWorkHours(start, end, null); } public int getTotalWorkHours(LocalDate start, LocalDate endExclusive, ICriterion criterion) { return getTotalEffortFor(IntraDayDate.startOfDay(start), IntraDayDate.startOfDay(endExclusive), criterion) .roundToHours(); } public EffortDuration getTotalEffortFor(IntraDayDate startInclusive, IntraDayDate endExclusive) { return getTotalEffortFor(startInclusive, endExclusive, null); } public EffortDuration getTotalEffortFor(IntraDayDate startInclusive, IntraDayDate endExclusive, ICriterion criterion) { return getTotalEffortFor(getCalendarOrDefault(), startInclusive, endExclusive, criterion); } public ICalendar getCalendarOrDefault() { return getCalendar() != null ? getCalendar() : SameWorkHoursEveryDay .getDefaultWorkingDay(); } private EffortDuration getTotalEffortFor(final ICalendar calendar, IntraDayDate startInclusive, IntraDayDate endExclusive, final ICriterion criterionToSatisfy) { Iterable<PartialDay> daysBetween = startInclusive .daysUntil(endExclusive); return EffortDuration.sum(daysBetween, new IEffortFrom<PartialDay>() { @Override public EffortDuration from(PartialDay current) { EffortDuration capacityCurrent = calendar .getCapacityOn(current); if (capacityCurrent != null && (criterionToSatisfy == null || satisfiesCriterionAt( criterionToSatisfy, current.getDate()))) { return capacityCurrent; } return zero(); } }); } private boolean satisfiesCriterionAt(ICriterion criterionToSatisfy, LocalDate current) { return criterionToSatisfy.isSatisfiedBy(this, current); } public void addUnvalidatedSatisfaction(CriterionSatisfaction criterionSatisfaction) { criterionSatisfactions.add(criterionSatisfaction); } public void addSatisfactions(Set<CriterionSatisfaction> addlist) throws ValidationException { //Create a newList with new Satisfactions and the old satisfactions Set<CriterionSatisfaction> newList = new HashSet<CriterionSatisfaction>(addlist); for(CriterionSatisfaction satisfaction : criterionSatisfactions){ if( !newList.contains(satisfaction) ){ newList.add(satisfaction); } } //Create a activeList with not eliminated Satifaction Set<CriterionSatisfaction> activeList = new HashSet<CriterionSatisfaction>(); for(CriterionSatisfaction satisfaction : addlist){ if( !satisfaction.isIsDeleted() ){ activeList.add(satisfaction); } } validateSatisfactions(activeList); criterionSatisfactions.clear(); criterionSatisfactions.addAll(newList); } private void validateSatisfactions(Set<CriterionSatisfaction> satisfactions) throws ValidationException { for (CriterionSatisfaction satisfaction : satisfactions) { final Set<CriterionSatisfaction> remainingSatisfactions = new HashSet<CriterionSatisfaction>(); remainingSatisfactions.addAll(satisfactions); remainingSatisfactions.remove(satisfaction); validateSatisfaction(satisfaction, remainingSatisfactions); } } private void validateSatisfaction(CriterionSatisfaction satisfaction, Set<CriterionSatisfaction> satisfactions) throws ValidationException { if ( !canAddSatisfaction(satisfaction, satisfactions) ) { String message = getReasonForNotAddingSatisfaction(satisfaction.getCriterion().getType()); throw new ValidationException(invalidValue(message, "resource", this, satisfaction)); } } private boolean canAddSatisfaction(CriterionSatisfaction satisfaction, Set<CriterionSatisfaction> satisfactions) { final ICriterionType<?> type = satisfaction.getCriterion().getType(); return canAddSatisfaction(type, satisfaction, satisfactions); } public boolean satisfiesCriterions(Collection<? extends ICriterion> criterions) { ICriterion compositedCriterion = CriterionCompounder.buildAnd(criterions).getResult(); return compositedCriterion.isSatisfiedBy(this); } public boolean satisfiesCriterionsAtSomePoint(Collection<? extends Criterion> criterions) { AvailabilityTimeLine availability = AvailabilityCalculator.getCriterionsAvailabilityFor(criterions, this); return !availability.getValidPeriods().isEmpty(); } @Valid public Set<ResourcesCostCategoryAssignment> getResourcesCostCategoryAssignments() { return resourcesCostCategoryAssignments; } public ResourcesCostCategoryAssignment getResourcesCostCategoryAssignmentByCode(String code) throws InstanceNotFoundException { if (StringUtils.isBlank(code)) { throw new InstanceNotFoundException(code, ResourcesCostCategoryAssignment.class.getName()); } for (ResourcesCostCategoryAssignment i : resourcesCostCategoryAssignments) { if (i.getCode().equalsIgnoreCase(StringUtils.trim(code))) { return i; } } throw new InstanceNotFoundException(code, ResourcesCostCategoryAssignment.class.getName()); } public void addResourcesCostCategoryAssignment(ResourcesCostCategoryAssignment assignment) { resourcesCostCategoryAssignments.add(assignment); if (assignment.getResource() != this) { assignment.setResource(this); } } public void addUnvalidatedResourcesCostCategoryAssignment( ResourcesCostCategoryAssignment assignment) { resourcesCostCategoryAssignments.add(assignment); } public void removeResourcesCostCategoryAssignment(ResourcesCostCategoryAssignment assignment) { resourcesCostCategoryAssignments.remove(assignment); if (assignment.getResource() == this) { assignment.setResource(null); } } @AssertTrue(message="Some criterion satisfactions overlap in time") public boolean isCriterionSatisfactionsOverlappingConstraint() { /* * Check if time intervals in criterion satisfactions are correct in * isolation. If not, it does not make sense to check criterion * satisfaction overlapping. */ for (CriterionSatisfaction i : getCriterionSatisfactions()) { if ( !(i.isStartDateSpecified() && i.isPositiveTimeInterval()) ) { return true; } } /* * Check assignment overlapping. */ try { checkNotOverlaps(); } catch (IllegalArgumentException e) { return false; } return true; } @AssertFalse(message="Some cost category assignments overlap in time") public boolean isAssignmentsOverlappingConstraint() { /* * Check if time intervals in cost assignments are correct in isolation. * If not, it does not make sense to check assignment overlapping. */ for (ResourcesCostCategoryAssignment each : getResourcesCostCategoryAssignments()) { if ( !(each.isInitDateSpecified() && each.isPositiveTimeInterval()) ) { return false; } } /* * Check assignment overlapping. */ List<ResourcesCostCategoryAssignment> assignmentsList = new ArrayList<ResourcesCostCategoryAssignment>(); assignmentsList.addAll(getResourcesCostCategoryAssignments()); try { CostCategory.validateCostCategoryOverlapping(assignmentsList); } catch (ValidationException e) { return true; } return false; } public abstract ResourceEnum getType(); public boolean isVirtual() { return false; } @AssertTrue(message="there exist criterion satisfactions referring to " + "criterion types not applicable to this resource") public boolean isCriterionSatisfactionsWithCorrectTypeConstraint() { for (CriterionSatisfaction c : getCriterionSatisfactions()) { if (!isCriterionSatisfactionOfCorrectType(c)) { return false; } } return true; } @AssertTrue(message="criterion satisfaction codes must be unique inside " + "a resource") public boolean isNonRepeatedCriterionSatisfactionCodesConstraint() { return getFirstRepeatedCode(criterionSatisfactions) == null; } @AssertTrue(message="resource cost category assignments codes must be " + "unique inside a resource") public boolean isNonRepeatedResourcesCostCategoryAssignmentCodesConstraint() { return getFirstRepeatedCode(resourcesCostCategoryAssignments) == null; } protected abstract boolean isCriterionSatisfactionOfCorrectType( CriterionSatisfaction c); protected IResourceDAO getIntegrationEntityDAO() { return Registry.getResourceDAO(); } public Boolean isLimitingResource() { return (resourceType == ResourceType.LIMITING_RESOURCE); } public ResourceType getResourceType() { return resourceType; } public void setResourceType(ResourceType resourceType) { this.resourceType = resourceType; } public LimitingResourceQueue getLimitingResourceQueue() { return limitingResourceQueue; } public void setLimitingResourceQueue(LimitingResourceQueue limitingResourceQueue) { limitingResourceQueue.setResource(this); this.limitingResourceQueue = limitingResourceQueue; } @Override public int compareTo(Resource resource) { return this.getShortDescription().compareToIgnoreCase( resource.getShortDescription()); } @AssertTrue(message = "You have exceeded the maximum limit of resources") public boolean isMaxResourcesConstraint() { return Registry.getTransactionService().runOnAnotherReadOnlyTransaction(new IOnTransaction<Boolean>() { @Override public Boolean execute() { Configuration configuration = Registry.getConfigurationDAO().getConfiguration(); if ( configuration == null ) { return true; } Integer maxResources = configuration.getMaxResources(); if ( maxResources != null && maxResources > 0 ) { List<Resource> resources = Registry.getResourceDAO().findAll(); int resourcesNumber = resources.size(); if ( isNewObject() ) { resourcesNumber++; } if ( resourcesNumber > maxResources ) { return false; } } return true; } }); } public boolean isActiveBetween(LocalDate startDate, LocalDate endDate) { return calendar.isActiveBetween(startDate, endDate); } }