ItEr11S13ProbasModuloRecursos: Satisfactions validation before saving and simple validation in interface.

This commit is contained in:
Óscar González Fernández 2009-06-08 10:59:33 +02:00 committed by Javier Moran Rua
parent 668a449b41
commit b06347a650
10 changed files with 308 additions and 62 deletions

View file

@ -4,6 +4,7 @@ import java.util.Comparator;
import java.util.Date;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
* Declares a interval of time in which the criterion is satisfied <br />
@ -63,6 +64,20 @@ public class CriterionSatisfaction {
private Resource resource;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
public CriterionSatisfaction copy() {
CriterionSatisfaction result = new CriterionSatisfaction();
result.startDate = startDate;
result.finishDate = finishDate;
result.criterion = criterion;
result.resource = resource;
return result;
}
public Date getStartDate() {
return startDate != null ? new Date(startDate.getTime()) : null;
}
@ -119,15 +134,10 @@ public class CriterionSatisfaction {
}
public void setEndDate(Date date) {
if (date == null) {
finishDate = null;
}
if ((startDate.equals(date) || startDate.before(date)))
finishDate = date;
}
public void setStartDate(Date date) {
if ((finishDate == null || finishDate.after(date)))
startDate = date;
}
@ -135,4 +145,15 @@ public class CriterionSatisfaction {
return getInterval().overlapsWith(interval);
}
public boolean goesBeforeWithoutOverlapping(CriterionSatisfaction other) {
int compare = BY_START_COMPARATOR.compare(this, other);
if (compare > 0) {
return false;
} else {
Interval thisInterval = getInterval();
return !thisInterval.overlapsWith(other.getInterval());
}
}
}

View file

@ -109,6 +109,12 @@ class Range extends Interval {
.compareTo(interval.start) > 0);
}
@Override
public String toString() {
return new StringBuilder("[").append(start).append(", ").append(end)
.append(")").toString();
}
}
class OpenEndedInterval extends Interval {
@ -131,6 +137,11 @@ class OpenEndedInterval extends Interval {
return start.before(interval.start) || interval.end == null
|| start.before(interval.end);
}
@Override
public String toString() {
return new StringBuilder("[").append(start).append(",...)").toString();
}
}
class Point extends Interval {
@ -154,4 +165,10 @@ class Point extends Interval {
return interval.contains(end) && !interval.start.equals(end);
}
@Override
public String toString() {
return new StringBuilder().append("[").append(start).append(")")
.toString();
}
}

View file

@ -1,12 +1,14 @@
package org.navalplanner.business.resources.entities;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.apache.commons.lang.Validate;
@ -142,6 +144,25 @@ public abstract class Resource {
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 Predicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
for (ICriterionType<?> criterionType : types) {
if (criterionType.contains(satisfaction.getCriterion())) {
return true;
}
}
return false;
}
});
}
}
public Query query() {
@ -245,8 +266,8 @@ public abstract class Resource {
}
public CriterionSatisfaction addSatisfaction(
ICriterionType<Criterion> type, CriterionSatisfaction satisfaction) {
public CriterionSatisfaction addSatisfaction(ICriterionType<?> type,
CriterionSatisfaction satisfaction) {
return new EnsureSatisfactionIsCorrect(this, type, satisfaction)
.addSatisfaction();
}
@ -347,7 +368,8 @@ public abstract class Resource {
public boolean canAddSatisfaction(ICriterionType<?> type,
CriterionSatisfaction satisfaction) {
return new EnsureSatisfactionIsCorrect(this, type, satisfaction)
EnsureSatisfactionIsCorrect ensureSatisfactionIsCorrect = new EnsureSatisfactionIsCorrect(this, type, satisfaction);
return ensureSatisfactionIsCorrect
.canAddSatisfaction();
}
@ -371,8 +393,7 @@ public abstract class Resource {
return previous;
}
public void removeCriterionSatisfaction(CriterionSatisfaction satisfaction)
throws InstanceNotFoundException {
public void removeCriterionSatisfaction(CriterionSatisfaction satisfaction) {
criterionSatisfactions.remove(satisfaction);
}
@ -380,4 +401,66 @@ public abstract class Resource {
return criterionSatisfactions.contains(satisfaction);
}
public void checkNotOverlaps(List<ICriterionType<?>> types) {
for (ICriterionType<?> criterionType : types) {
if (!criterionType.allowSimultaneousCriterionsPerResource()) {
List<CriterionSatisfaction> satisfactions = query().from(
criterionType).sortByStartDate().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);
}
}
}
}
private void checkNotOverlaps(CriterionSatisfaction before,
CriterionSatisfaction after) {
if (!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();
}
}
}

View file

@ -4,7 +4,7 @@ import java.util.List;
import java.util.Set;
import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
//import org.navalplanner.business.resources.entities.CriterionSatisfaction;
import org.navalplanner.business.common.exceptions.ValidationException;
import org.navalplanner.business.resources.entities.ICriterion;
import org.navalplanner.business.resources.entities.Resource;
import org.navalplanner.business.resources.entities.Worker;
@ -19,6 +19,7 @@ public interface ResourceService {
* It updates or inserts the resource passed as a parameter. If the resource
* is a composite resource, updating or inserting is cascaded to the
* resources contained in it.
* @throws ValidationException
*/
public void saveResource(Resource resource);

View file

@ -5,9 +5,10 @@ import java.util.List;
import java.util.Set;
import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
import org.navalplanner.business.resources.bootstrap.ICriterionsBootstrap;
import org.navalplanner.business.resources.daos.IResourceDao;
import org.navalplanner.business.resources.entities.ICriterion;
import org.navalplanner.business.resources.entities.ICriterionType;
import org.navalplanner.business.resources.entities.Resource;
import org.navalplanner.business.resources.entities.Worker;
import org.navalplanner.business.resources.services.ResourceService;
@ -25,10 +26,20 @@ public class ResourceServiceImpl implements ResourceService {
@Autowired
private IResourceDao resourceDao;
@Autowired
private ICriterionsBootstrap criterionsBootstrap;
@Transactional
public void saveResource(Resource resource) {
checkResourceIsOk(resource);
resourceDao.save(resource);
}
private void checkResourceIsOk(Resource resource) {
List<ICriterionType<?>> types = criterionsBootstrap.getTypes();
resource.checkNotOverlaps(types);
}
@Transactional(readOnly = true)
public Resource findResource(Long resourceId)
throws InstanceNotFoundException {

View file

@ -127,4 +127,37 @@ public class CriterionSatisfactionTest {
assertThat(copy, equalTo(orderedSatisfactions));
}
}
@Test
public void testGoesBeforeWithoutOverlapping() {
final Criterion criterion = CriterionDAOTest.createValidCriterion();
Worker worker = new Worker("firstName", "surName", "2333232", 10);
CriterionSatisfaction posterior = new CriterionSatisfaction();
posterior.setCriterion(criterion);
posterior.setStartDate(year(2000));
posterior.setEndDate(year(2008));
Interval[] goesAfterOrOverlapsIntervals = { Interval.from(year(2000)),
Interval.from(year(2001)), Interval.from(year(1999)),
Interval.range(year(1999), year(2001)),
Interval.from(year(2009)),
Interval.range(year(2009), year(2012)) };
for (Interval interval : goesAfterOrOverlapsIntervals) {
CriterionSatisfaction copied = posterior.copy();
copied.setStartDate(interval.getStart());
copied.setEndDate(interval.getEnd());
assertFalse(interval + " shouldn't go before", copied
.goesBeforeWithoutOverlapping(posterior));
}
Interval[] goesBeforeWithoutOverlappingInterval = {
Interval.point(year(2000)),
Interval.range(year(1990), year(2000)),
Interval.range(year(1990), year(1997)) };
for (Interval interval : goesBeforeWithoutOverlappingInterval) {
CriterionSatisfaction copied = posterior.copy();
copied.setStartDate(interval.getStart());
copied.setEndDate(interval.getEnd());
assertTrue(copied.goesBeforeWithoutOverlapping(posterior));
}
}
}

View file

@ -37,7 +37,15 @@ public interface IWorkerModel {
Map<ICriterionType<?>, Collection<Criterion>> getLaboralRelatedCriterions();
Set<CriterionSatisfaction> getLaboralRelatedCriterionSatisfactions(
List<CriterionSatisfaction> getLaboralRelatedCriterionSatisfactions(
Worker worker);
public enum AddingSatisfactionResult {
OK, SATISFACTION_WRONG, DONT_COMPLY_OVERLAPPING_RESTRICTIONS;
}
AddingSatisfactionResult addSatisfaction(ICriterionType<?> type,
CriterionSatisfaction originalSatisfaction,
CriterionSatisfaction edited);
}

View file

@ -3,9 +3,8 @@ package org.navalplanner.web.resources.worker;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
@ -13,8 +12,11 @@ import org.navalplanner.business.resources.entities.Criterion;
import org.navalplanner.business.resources.entities.CriterionSatisfaction;
import org.navalplanner.business.resources.entities.CriterionWithItsType;
import org.navalplanner.business.resources.entities.ICriterionType;
import org.navalplanner.business.resources.entities.Interval;
import org.navalplanner.business.resources.entities.Worker;
import org.navalplanner.web.common.IMessagesForUser;
import org.navalplanner.web.common.Level;
import org.navalplanner.web.common.Util;
import org.navalplanner.web.resources.worker.IWorkerModel.AddingSatisfactionResult;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Listbox;
@ -34,26 +36,28 @@ public class WorkRelationshipsController extends GenericForwardComposer {
* CriterionSatisfaction();
*/
private CriterionSatisfaction editRelationship = new CriterionSatisfaction();
private CriterionSatisfaction satisfactionEdited = new CriterionSatisfaction();
private Collection<Criterion> workCriterions;
private List<Criterion> workCriterions;
private Listbox selectedWorkCriterion;
/*
* private Datebox newWorkRelationshipStartDate;
*
* private Datebox newWorkRelationshipEndDate;
*/
private HashMap<Criterion, CriterionWithItsType> fromCriterionToType;
private boolean editing;
private Component containerComponent;
private CriterionSatisfaction originalSatisfaction;
private final IMessagesForUser messagesForUser;
public WorkRelationshipsController(IWorkerModel workerModel,
WorkerCRUDController workerCRUDController) {
WorkerCRUDController workerCRUDController,
IMessagesForUser messagesForUser) {
this.workerModel = workerModel;
this.workerCRUDController = workerCRUDController;
this.messagesForUser = messagesForUser;
this.workCriterions = new ArrayList<Criterion>();
Map<ICriterionType<?>, Collection<Criterion>> map = workerModel
.getLaboralRelatedCriterions();
@ -68,13 +72,12 @@ public class WorkRelationshipsController extends GenericForwardComposer {
}
}
public Set<CriterionSatisfaction> getCriterionSatisfactions() {
if (this.workerCRUDController.getWorker() == null) {
return new HashSet<CriterionSatisfaction>();
public List<CriterionSatisfaction> getCriterionSatisfactions() {
if (getWorker() == null) {
return new ArrayList<CriterionSatisfaction>();
} else {
return workerModel
.getLaboralRelatedCriterionSatisfactions(this.workerCRUDController
.getWorker());
.getLaboralRelatedCriterionSatisfactions(getWorker());
}
}
@ -86,47 +89,88 @@ public class WorkRelationshipsController extends GenericForwardComposer {
}
public void prepareForCreate() {
this.editRelationship = new CriterionSatisfaction();
this.satisfactionEdited = new CriterionSatisfaction();
this.originalSatisfaction = this.satisfactionEdited;
Util.reloadBindings(containerComponent);
editing = false;
}
public void prepareForEdit(CriterionSatisfaction criterionSatisfaction) {
this.editRelationship = criterionSatisfaction;
this.satisfactionEdited = criterionSatisfaction.copy();
this.originalSatisfaction = criterionSatisfaction;
Util.reloadBindings(containerComponent);
this.satisfactionEdited.setCriterion(select(this.satisfactionEdited
.getCriterion()));
// the criterion retrieved is used instead of the original one, so the
// call fromCriterionToType.get(criterion) works
editing = true;
}
public void saveCriterionSatisfaction() throws InstanceNotFoundException {
// Add new criterion
Criterion selectedCriterion = (Criterion) selectedWorkCriterion
.getSelectedItem().getValue();
CriterionWithItsType criterionWithItsType = fromCriterionToType
.get(selectedCriterion);
System.out.println("SAVE!!: " + selectedCriterion.getName());
if (this.workerCRUDController.getWorker().contains(editRelationship)) {
this.workerCRUDController.getWorker().removeCriterionSatisfaction(
editRelationship);
private Criterion select(Criterion criterion) {
int i = 0;
for (Criterion c : workCriterions) {
if (c.isEquivalent(criterion)) {
selectedWorkCriterion.setSelectedIndex(i);
return c;
}
i++;
}
throw new RuntimeException("not found criterion" + criterion);
}
this.workerCRUDController.getWorker().addSatisfaction(
criterionWithItsType,
Interval.range(editRelationship.getStartDate(),
editRelationship.getEndDate()));
// Delete the former one
workerCRUDController.getWorker().removeCriterionSatisfaction(
this.editRelationship);
public void saveCriterionSatisfaction() {
Criterion choosenCriterion = getChoosenCriterion();
CriterionWithItsType criterionWithItsType = fromCriterionToType
.get(choosenCriterion);
satisfactionEdited.setCriterion(choosenCriterion);
AddingSatisfactionResult addSatisfaction = workerModel.addSatisfaction(
criterionWithItsType.getType(), originalSatisfaction,
satisfactionEdited);
switch (addSatisfaction) {
case OK:
messagesForUser.showMessage(Level.INFO, "Periodo gardado");
this.workerCRUDController.goToEditForm();
break;
case SATISFACTION_WRONG:
messagesForUser
.showMessage(Level.WARNING,
"O periodo ten datos inválidos. A fecha de fin debe ser posterior á de inicio");
break;
case DONT_COMPLY_OVERLAPPING_RESTRICTIONS:
messagesForUser
.showMessage(Level.WARNING,
"O periodo non se puido gardar. Solápase cun periodo non compatible.");
this.workerCRUDController.goToEditForm();
break;
default:
throw new RuntimeException("unexpected: " + addSatisfaction);
}
}
private Criterion getChoosenCriterion() {
Criterion criterion;
if (editing) {
criterion = satisfactionEdited.getCriterion();
} else {
criterion = (Criterion) this.selectedWorkCriterion
.getSelectedItemApi().getValue();
}
return criterion;
}
private Worker getWorker() {
return this.workerModel.getWorker();
}
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
this.containerComponent = comp;
this.selectedWorkCriterion.setSelectedIndex(0);
}
public CriterionSatisfaction getEditRelationship() {
return this.editRelationship;
return this.satisfactionEdited;
}
public Collection<Criterion> getWorkCriterions() {

View file

@ -130,7 +130,6 @@ public class WorkerCRUDController extends GenericForwardComposer implements
public void goToAddWorkRelationshipForm() {
this.addWorkRelationship.prepareForCreate();
getVisibility().showOnly(addWorkRelationshipWindow);
Util.reloadBindings(addWorkRelationshipWindow);
}
public void goToCreateForm() {
@ -142,7 +141,6 @@ public class WorkerCRUDController extends GenericForwardComposer implements
public void goToEditWorkRelationshipForm(CriterionSatisfaction satisfaction) {
this.editWorkRelationship.prepareForEdit(satisfaction);
getVisibility().showOnly(editWorkRelationshipWindow);
Util.reloadBindings(editWorkRelationshipWindow);
}
@Override
@ -158,12 +156,13 @@ public class WorkerCRUDController extends GenericForwardComposer implements
throw new RuntimeException("messagesContainer is needed");
messages = new MessagesForUser(messagesContainer);
this.addWorkRelationship = new WorkRelationshipsController(
this.workerModel, this);
this.workerModel, this, messages);
setupWorkRelationshipController(this.addWorkRelationship,
this.addWorkRelationshipWindow);
setupWorkRelationshipController(
this.editWorkRelationship = new WorkRelationshipsController(
this.workerModel, this), editWorkRelationshipWindow);
this.workerModel, this, messages),
editWorkRelationshipWindow);
URLHandler<IWorkerCRUDControllerEntryPoints> handler = URLHandlerRegistry
.getRedirectorFor(IWorkerCRUDControllerEntryPoints.class);

View file

@ -110,6 +110,34 @@ public class WorkerModel implements IWorkerModel {
return worker.getAllSatisfactions();
}
@Transactional
public AddingSatisfactionResult addSatisfaction(ICriterionType<?> type,
CriterionSatisfaction original, CriterionSatisfaction edited) {
Worker worker = getWorker();
edited.setResource(worker);
boolean previouslyContained = false;
if (previouslyContained = worker.contains(original)) {
worker.removeCriterionSatisfaction(original);
}
boolean canAdd = false;
try {
canAdd = worker.canAddSatisfaction(type, edited);
} catch (IllegalArgumentException e) {
if (previouslyContained) {
worker.addSatisfaction(type, original);
}
return AddingSatisfactionResult.SATISFACTION_WRONG;
}
if (!canAdd) {
if (previouslyContained) {
worker.addSatisfaction(type, original);
}
return AddingSatisfactionResult.DONT_COMPLY_OVERLAPPING_RESTRICTIONS;
}
worker.addSatisfaction(type, edited);
return AddingSatisfactionResult.OK;
}
private static class NullAssigner implements
IMultipleCriterionActiveAssigner {
@ -252,7 +280,8 @@ public class WorkerModel implements IWorkerModel {
public void applyChanges() {
for (CriterionSatisfaction criterionSatisfaction : added) {
resource.addSatisfaction(new CriterionWithItsType(type,
criterionSatisfaction.getCriterion()), Interval.from(criterionSatisfaction.getStartDate()));
criterionSatisfaction.getCriterion()), Interval
.from(criterionSatisfaction.getStartDate()));
}
for (Criterion criterion : unassigned.keySet()) {
resource.finish(new CriterionWithItsType(type, criterion));
@ -307,13 +336,13 @@ public class WorkerModel implements IWorkerModel {
}
@Override
public Set<CriterionSatisfaction> getLaboralRelatedCriterionSatisfactions(
public List<CriterionSatisfaction> getLaboralRelatedCriterionSatisfactions(
Worker worker) {
Set<CriterionSatisfaction> result = new HashSet<CriterionSatisfaction>();
for (ICriterionType<?> criterionType : laboralRelatedTypes) {
result.addAll(worker.getSatisfactionsFor(criterionType));
}
return result;
return worker.query().oneOf(laboralRelatedTypes).sortByStartDate()
.result();
}
}