diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java index 3f1cc5fb8..a13d0ae61 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java @@ -18,10 +18,34 @@ import org.navalplanner.business.common.exceptions.InstanceNotFoundException; public interface IGenericDao { /** - * It updates and inserts the object passed as a parameter. + * It updates or inserts the instance passed as a parameter. If the + * instance passed as parameter already exists in the database, the + * instance is reattached to the underlying ORM session. In this case, if + * the version field is older than the one in the database, + * org.springframework.dao.OptimisticLockingFailureException + * is thrown. */ public void save(E entity); + /** + * It reattaches the instance passed as a parameter to the underlying ORM + * session. It can only be used in READ-ONLY transactions. If the + * version field is older than the one in the database, + * org.springframework.dao.OptimisticLockingFailureException + * is thrown. + */ + public void reattachForRead(E entity); + + /** + * It sets a WRITE lock on the instance passed as a parameter. The instance + * must exist in the database. Other concurrent transactions will be + * blocked if they try to write (but can read) on the same persistent + * instance. If the version field is older than the one in the database, + * org.springframework.dao.OptimisticLockingFailureException + * is thrown. The lock is released when the transaction finishes. + */ + public void lock(E entity); + public E find(PK id) throws InstanceNotFoundException; public boolean exists(PK id); diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java index b2697b07f..f2e81e8c5 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java @@ -6,6 +6,7 @@ import java.util.List; import org.apache.commons.lang.Validate; import org.hibernate.HibernateException; +import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Projections; @@ -74,6 +75,26 @@ public class GenericDaoHibernate implements } + public void reattachForRead(E entity) { + + try { + getSession().lock(entity, LockMode.READ); + } catch (HibernateException e) { + throw convertHibernateAccessException(e); + } + + } + + public void lock(E entity) { + + try { + getSession().lock(entity, LockMode.UPGRADE); + } catch (HibernateException e) { + throw convertHibernateAccessException(e); + } + + } + @SuppressWarnings("unchecked") public E find(PK id) throws InstanceNotFoundException { diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java index d3305e2be..ba2b7b226 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java @@ -4,6 +4,7 @@ import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.util.List; +import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Projections; @@ -59,6 +60,14 @@ public class GenericDaoHibernateTemplate implements hibernateTemplate.saveOrUpdate(entity); } + public void reattachForRead(E entity) { + hibernateTemplate.lock(entity, LockMode.READ); + } + + public void lock(E entity) { + hibernateTemplate.lock(entity, LockMode.UPGRADE); + } + @SuppressWarnings("unchecked") public E find(PK id) throws InstanceNotFoundException { diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java index 666cd1a28..afb553d2e 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java @@ -23,6 +23,14 @@ public interface ResourceService { */ public void saveResource(Resource resource); + /** + * It checks if the version of the detached object passed as a parameter is + * older than the one in the database. If it is older, it throws + * org.springframework.dao.OptimisticLockingFailureException. + * It can not be called as part of a READ-WRITE transaction. + */ + public void checkVersion(Resource resource); + public Resource findResource(Long resourceId) throws InstanceNotFoundException; diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java index 4755888a4..330f5dc02 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java @@ -35,6 +35,11 @@ public class ResourceServiceImpl implements ResourceService { resourceDao.save(resource); } + @Transactional(readOnly = true) + public void checkVersion(Resource resource) { + resourceDao.reattachForRead(resource); + } + private void checkResourceIsOk(Resource resource) { List> types = criterionsBootstrap.getTypes(); resource.checkNotOverlaps(types); diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java index 31635faf3..c93276151 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java @@ -3,7 +3,6 @@ package org.navalplanner.web.resources.worker; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; import org.navalplanner.business.common.exceptions.ValidationException; import org.navalplanner.business.resources.entities.Criterion; @@ -31,14 +30,9 @@ public interface IWorkerModel { boolean isCreating(); - Set getCriterionSatisfactions(Worker worker); - - Worker findResource(long workerId); - Map, Collection> getLaboralRelatedCriterions(); - List getLaboralRelatedCriterionSatisfactions( - Worker worker); + List getLaboralRelatedCriterionSatisfactions(); public enum AddingSatisfactionResult { OK, SATISFACTION_WRONG, DONT_COMPLY_OVERLAPPING_RESTRICTIONS; @@ -48,4 +42,11 @@ public interface IWorkerModel { CriterionSatisfaction originalSatisfaction, CriterionSatisfaction edited); + void removeSatisfaction(CriterionSatisfaction satisfaction); + + public void assignCriteria(Collection criteria); + + void unassignSatisfactions( + Collection satisfactions); + } \ No newline at end of file diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/LocalizationsController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/LocalizationsController.java index 15960fe22..5b389d6ec 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/LocalizationsController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/LocalizationsController.java @@ -71,7 +71,7 @@ public class LocalizationsController extends GenericForwardComposer { @Override public void onEvent(Event event) throws Exception { - workerModel.getLocalizationsAssigner().unassign( + workerModel.unassignSatisfactions( extractValuesOf(activeSatisfactions.getSelectedItems(), CriterionSatisfaction.class)); reloadLists(); @@ -84,7 +84,7 @@ public class LocalizationsController extends GenericForwardComposer { public void onEvent(Event event) throws Exception { Set selectedItems = criterionsNotAssigned .getSelectedItems(); - workerModel.getLocalizationsAssigner().assign( + workerModel.assignCriteria( extractValuesOf(selectedItems, Criterion.class)); reloadLists(); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java index 9cd0a6945..840e83bd0 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java @@ -77,14 +77,13 @@ public class WorkRelationshipsController extends GenericForwardComposer { return new ArrayList(); } else { return workerModel - .getLaboralRelatedCriterionSatisfactions(getWorker()); + .getLaboralRelatedCriterionSatisfactions(); } } public void deleteCriterionSatisfaction(CriterionSatisfaction satisfaction) throws InstanceNotFoundException { - workerCRUDController.getWorker().removeCriterionSatisfaction( - satisfaction); + workerModel.removeSatisfaction(satisfaction); this.workerCRUDController.goToEditForm(); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java index 5ad091953..a8eb89c42 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java @@ -106,14 +106,15 @@ public class WorkerModel implements IWorkerModel { } } - public Set getCriterionSatisfactions(Worker worker) { - return worker.getAllSatisfactions(); - } - - @Transactional + @Transactional(readOnly = true) public AddingSatisfactionResult addSatisfaction(ICriterionType type, CriterionSatisfaction original, CriterionSatisfaction edited) { + + /* Check worker's version. */ Worker worker = getWorker(); + resourceService.checkVersion(worker); + + /* Add criterion satisfaction. */ edited.setResource(worker); boolean previouslyContained = false; if (previouslyContained = worker.contains(original)) { @@ -138,6 +139,42 @@ public class WorkerModel implements IWorkerModel { return AddingSatisfactionResult.OK; } + @Transactional(readOnly = true) + public void removeSatisfaction(CriterionSatisfaction satisfaction) { + + /* Check worker's version. */ + Worker worker = getWorker(); + resourceService.checkVersion(worker); + + /* Remove criterion satisfaction. */ + worker.removeCriterionSatisfaction(satisfaction); + + } + + @Transactional(readOnly = true) + public void assignCriteria(Collection criteria) { + + /* Check worker's version. */ + Worker worker = getWorker(); + resourceService.checkVersion(worker); + + /* Assign criteria. */ + getLocalizationsAssigner().assign(criteria); + } + + @Transactional(readOnly = true) + public void unassignSatisfactions( + Collection satisfactions) { + + /* Check worker's version. */ + Worker worker = getWorker(); + resourceService.checkVersion(worker); + + /* Unassign criterion satisfactions. */ + getLocalizationsAssigner().unassign(satisfactions); + + } + private static class NullAssigner implements IMultipleCriterionActiveAssigner { @@ -317,16 +354,6 @@ public class WorkerModel implements IWorkerModel { } @Override - public Worker findResource(long workerId) { - try { - return (Worker) resourceService.findResource(workerId); - } catch (InstanceNotFoundException e) { - throw new RuntimeException(e); - } - } - - @Override - @Transactional(readOnly = true) public Map, Collection> getLaboralRelatedCriterions() { Map, Collection> result = new HashMap, Collection>(); for (ICriterionType type : laboralRelatedTypes) { @@ -336,12 +363,8 @@ public class WorkerModel implements IWorkerModel { } @Override - public List getLaboralRelatedCriterionSatisfactions( - Worker worker) { - Set result = new HashSet(); - for (ICriterionType criterionType : laboralRelatedTypes) { - result.addAll(worker.getSatisfactionsFor(criterionType)); - } + public List + getLaboralRelatedCriterionSatisfactions() { return worker.query().oneOf(laboralRelatedTypes).sortByStartDate() .result(); }