diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java index a966d924ac0287955ad18ff755dc5c15512d3dba..dec028453e76e0892af7fc5a0dfa91d637c13a44 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java @@ -16,7 +16,6 @@ package ch.systemsx.cisd.openbis.generic.client.web.client; -import java.util.Date; import java.util.List; import java.util.Map; @@ -664,7 +663,7 @@ public interface ICommonClientService extends IClientService /** * Updates project. */ - public Date updateProject(ProjectUpdates updates) throws UserFailureException; + public int updateProject(ProjectUpdates updates) throws UserFailureException; /** Deletes/Trashes the specified data sets. */ public void deleteDataSets( diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java index 6a6f409f265aadb67b47e750b2ed5d1915747531..f7038879ee2574894f84f2dc4baad6c065a2562c 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java @@ -16,7 +16,6 @@ package ch.systemsx.cisd.openbis.generic.client.web.client; -import java.util.Date; import java.util.List; import java.util.Map; @@ -696,7 +695,7 @@ public interface ICommonClientServiceAsync extends IClientServiceAsync /** * @see ICommonClientService#updateProject(ProjectUpdates) */ - public void updateProject(ProjectUpdates updates, AsyncCallback<Date> projectEditCallback); + public void updateProject(ProjectUpdates updates, AsyncCallback<Integer> projectEditCallback); /** * @see ICommonClientService#deleteEntityTypes(EntityKind, List) diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/project/ProjectEditForm.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/project/ProjectEditForm.java index eb3bf270383cda342e2effd91d8717764801c45c..e3ee1c7aeef41aef17efbf9eacfeb5a6e4e9913c 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/project/ProjectEditForm.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/project/ProjectEditForm.java @@ -16,8 +16,6 @@ package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.project; -import java.util.Date; - import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientServiceAsync; import ch.systemsx.cisd.openbis.generic.client.web.client.application.AbstractAsyncCallback; import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext; @@ -63,7 +61,7 @@ public class ProjectEditForm extends AbstractProjectEditRegisterForm updates.setAttachmentSessionKey(sessionKey); updates.setDescription(projectDescriptionField.getValue()); updates.setTechId(projectId); - updates.setVersion(originalProject.getModificationDate()); + updates.setVersion(originalProject.getVersion()); Space space = spaceField.tryGetSelected(); updates.setGroupCode(space == null ? null : space.getCode()); @@ -71,7 +69,7 @@ public class ProjectEditForm extends AbstractProjectEditRegisterForm } private final class ProjectEditCallback extends - AbstractRegistrationForm.AbstractRegistrationCallback<Date> + AbstractRegistrationForm.AbstractRegistrationCallback<Integer> { ProjectEditCallback(final IViewContext<?> viewContext) @@ -80,15 +78,15 @@ public class ProjectEditForm extends AbstractProjectEditRegisterForm } @Override - protected void process(final Date result) + protected void process(final Integer result) { - originalProject.setModificationDate(result); + originalProject.setVersion(result); updateOriginalValues(); super.process(result); } @Override - protected String createSuccessfullRegistrationInfo(Date result) + protected String createSuccessfullRegistrationInfo(Integer result) { return "Project <b>" + originalProject.getCode() + "</b> successfully updated."; } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java index a27db3d39ab51ffc81afbcb10d809a5b9a674de6..7297eedad01a9a63c404cca6d94c949074821492 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java @@ -21,11 +21,11 @@ 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.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.HttpSession; @@ -42,9 +42,9 @@ import ch.systemsx.cisd.common.parser.IPropertyMapper; import ch.systemsx.cisd.common.parser.ParserException; import ch.systemsx.cisd.common.reflection.BeanUtils; import ch.systemsx.cisd.common.servlet.IRequestContextProvider; -import ch.systemsx.cisd.openbis.common.spring.IUncheckedMultipartFile; import ch.systemsx.cisd.common.string.ReflectingStringUnescaper; import ch.systemsx.cisd.common.string.UnicodeUtils; +import ch.systemsx.cisd.openbis.common.spring.IUncheckedMultipartFile; import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientService; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ArchivingResult; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DataSetUploadParameters; @@ -1638,11 +1638,11 @@ public final class CommonClientService extends AbstractClientService implements } @Override - public Date updateProject(final ProjectUpdates updates) + public int updateProject(final ProjectUpdates updates) throws ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException { final String sessionToken = getSessionToken(); - final Date modificationDate = new Date(); + final AtomicInteger version = new AtomicInteger(); new AttachmentRegistrationHelper() { @Override @@ -1650,11 +1650,11 @@ public final class CommonClientService extends AbstractClientService implements { ProjectUpdatesDTO updatesDTO = translate(updates); updatesDTO.setAttachments(attachments); - Date date = commonServer.updateProject(sessionToken, updatesDTO); - modificationDate.setTime(date.getTime()); + int versionNumber = commonServer.updateProject(sessionToken, updatesDTO); + version.set(versionNumber); } }.process(updates.getAttachmentSessionKey(), getHttpSession(), updates.getAttachments()); - return modificationDate; + return version.get(); } private static ProjectUpdatesDTO translate(ProjectUpdates updates) diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java index 57b89f1b15aad07a9ef32180665db47eddb87d81..f904bfd8703d390e1970051b3c7430c53a1632e2 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java @@ -2192,7 +2192,7 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt @Override @RolesAllowed(RoleWithHierarchy.SPACE_POWER_USER) @Capability("WRITE_PROJECT") - public Date updateProject(String sessionToken, + public int updateProject(String sessionToken, @AuthorizationGuard(guardClass = ProjectUpdatesPredicate.class) ProjectUpdatesDTO updates) { @@ -2200,7 +2200,7 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt final IProjectBO bo = businessObjectFactory.createProjectBO(session); bo.update(updates); bo.save(); - return bo.getProject().getModificationDate(); + return bo.getProject().getVersion(); } private void deleteEntityTypes(String sessionToken, EntityKind entityKind, List<String> codes) diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java index a729604eda6ece4364898b12279e3f632962052b..0faff1e3851f15c6d358ef6664adb970df3bf112 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java @@ -957,11 +957,11 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe } @Override - public Date updateProject(String sessionToken, ProjectUpdatesDTO updates) + public int updateProject(String sessionToken, ProjectUpdatesDTO updates) { logTracking(sessionToken, "edit_project", "PROJECT_ID(%s) ATTACHMENTS_ADDED(%s)", updates.getTechId(), updates.getAttachments().size()); - return null; + return 0; } @Override diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/RelationshipService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/RelationshipService.java index 23ef8dffcc6bbe082c64e29279d52336c7865fd0..c2780d326da90041f328637c24159d48706d1e9e 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/RelationshipService.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/RelationshipService.java @@ -16,6 +16,8 @@ package ch.systemsx.cisd.openbis.generic.server.business; +import java.util.Date; + import javax.annotation.Resource; import ch.systemsx.cisd.common.exception.UserFailureException; @@ -60,7 +62,21 @@ public class RelationshipService implements IRelationshipService ProjectPE project) { SampleUtils.setSamplesSpace(experiment, project.getSpace()); + PersonPE modifier = experiment.getModifier(); + ProjectPE previousProject = experiment.getProject(); + setModifierAndModificationDate(previousProject, modifier); experiment.setProject(project); + setModifierAndModificationDate(project, modifier); + } + + private void setModifierAndModificationDate(ProjectPE projectOrNull, PersonPE modifier) + { + if (projectOrNull == null) + { + return; + } + projectOrNull.setModifier(modifier); + projectOrNull.setModificationDate(new Date()); } @Override diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java index 3c9780d7a3fad25e833f79e1d074e8587f4d91a0..47cedb4968b43d87b274647ea5626984fa86dc19 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java @@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -129,8 +130,8 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper } } - private static final String PROPERTY_TYPES = - "experimentType.experimentTypePropertyTypesInternal"; + @Private + static final String PROPERTY_TYPES = "experimentType.experimentTypePropertyTypesInternal"; @Override public void loadDataByTechId(TechId experimentId) @@ -368,6 +369,8 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper { throw UserFailureException.fromTemplate(ERR_PROJECT_NOT_FOUND, newExperiment); } + project.setModificationDate(new Date()); + project.setModifier(findPerson()); experiment.setProject(project); } @@ -453,6 +456,7 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper { throwModifiedEntityException("Experiment"); } + experiment.setModifier(findPerson()); updateProperties(updates.getProperties()); ProjectPE project = findProject(updates.getProjectIdentifier()); @@ -594,23 +598,6 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper return new HashSet<String>(Arrays.asList(objects)); } - @Private - void updateProject(ProjectIdentifier newProjectIdentifier) - { - ProjectPE project = findProject(newProjectIdentifier); - ProjectPE previousProject = experiment.getProject(); - if (project.equals(previousProject)) - { - return; // nothing to change - } - // if the group has changes, move all samples to that group - if (project.getSpace().equals(previousProject.getSpace()) == false) - { - SampleUtils.setSamplesSpace(experiment, project.getSpace()); - } - experiment.setProject(project); - } - private ProjectPE findProject(ProjectIdentifier newProjectIdentifier) { ProjectPE project = diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentTable.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentTable.java index fcaea658d759870691857fca5f3867c489abf0d8..1f3623aa62778cd6b962d9920090f6b7162a42fc 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentTable.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentTable.java @@ -245,6 +245,7 @@ public final class ExperimentTable extends AbstractBusinessObject implements IEx "No experiment could be found with given identifier '%s'.", updates.getOldExperimentIdentifier()); } + experiment.setModifier(findPerson()); ExperimentBatchUpdateDetails details = updates.getDetails(); batchUpdateProperties(experiment, updates.getProperties(), details.getPropertiesToUpdate()); diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java index 69ef4e3b31755757ab5b04c836c8512f6d33ae1f..aa26f27ec146cc9fc0ab05c566bfe39f307f391a 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java @@ -288,7 +288,7 @@ public final class ProjectBO extends AbstractBusinessObject implements IProjectB public void update(ProjectUpdatesDTO updates) { loadDataByTechId(updates.getTechId()); - if (updates.getVersion().equals(project.getModificationDate()) == false) + if (updates.getVersion() != project.getVersion()) { throwModifiedEntityException("Project"); } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ProjectDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ProjectDAO.java index b9947b9c42da9abef35105928b40dbf4e6ff5428..9fb79de8ec70ea23fe57901553aca41076f3a10e 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ProjectDAO.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ProjectDAO.java @@ -16,6 +16,7 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess.db; +import java.util.Date; import java.util.List; import org.apache.commons.lang.StringUtils; @@ -125,6 +126,7 @@ public class ProjectDAO extends AbstractGenericEntityDAO<ProjectPE> implements I project.setCode(CodeConverter.tryToDatabase(project.getCode())); project.setModifier(modifier); + project.setModificationDate(new Date()); final HibernateTemplate template = getHibernateTemplate(); template.saveOrUpdate(project); template.flush(); diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java index e8da4b03115824ca0648dbbb0b0d07d9b83a03d4..aea34723bfe0f25f9272af93af18364787f0028d 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java @@ -878,7 +878,7 @@ public interface ICommonServer extends IServer */ @Transactional @DatabaseUpdateModification(value = ObjectKind.PROJECT) - public Date updateProject(String sessionToken, ProjectUpdatesDTO updates); + public int updateProject(String sessionToken, ProjectUpdatesDTO updates); /** * Deletes specified data set types. diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AbstractProjectUpdates.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AbstractProjectUpdates.java index 0151561be241d11877df73080c2f6ed5ac5905c0..bad71532d1279941a27c92b2f5f7bbebfb7a16a8 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AbstractProjectUpdates.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/AbstractProjectUpdates.java @@ -17,7 +17,6 @@ package ch.systemsx.cisd.openbis.generic.shared.basic.dto; import java.io.Serializable; -import java.util.Date; import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; @@ -31,7 +30,7 @@ public class AbstractProjectUpdates implements Serializable private static final long serialVersionUID = ServiceVersionHolder.VERSION; - private Date version; + private int version; private TechId id; @@ -52,12 +51,12 @@ public class AbstractProjectUpdates implements Serializable this.groupCodeOrNull = groupCode; } - public Date getVersion() + public int getVersion() { return version; } - public void setVersion(Date version) + public void setVersion(int version) { this.version = version; } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/CodeWithRegistrationAndModificationDate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/CodeWithRegistrationAndModificationDate.java index 6b2f25944eacc620c3468d436335f9ffe8edfd18..bfe5767ef4d54d4a0eb48c78a72404cfe04b6de6 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/CodeWithRegistrationAndModificationDate.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/CodeWithRegistrationAndModificationDate.java @@ -30,6 +30,8 @@ public class CodeWithRegistrationAndModificationDate<T extends CodeWithRegistrat private Date modificationDate; + private int version; + private Person modifier; public Date getModificationDate() @@ -52,4 +54,14 @@ public class CodeWithRegistrationAndModificationDate<T extends CodeWithRegistrat { this.modifier = modifier; } + + public int getVersion() + { + return version; + } + + public void setVersion(int version) + { + this.version = version; + } } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java index 946d0825c9d922ee236de2041fca4ff686300f87..73b3a47a521d06d886b7980c5a3f1f39308ac30c 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java @@ -47,6 +47,7 @@ import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; import org.hibernate.annotations.Generated; import org.hibernate.annotations.GenerationTime; +import org.hibernate.annotations.OptimisticLock; import org.hibernate.search.annotations.ContainedIn; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Index; @@ -110,6 +111,8 @@ public final class ProjectPE extends AttachmentHolderPE implements Comparable<Pr private Date modificationDate; + private int version; + @Column(name = ColumnNames.REGISTRATION_TIMESTAMP_COLUMN, nullable = false, insertable = false, updatable = false) @Generated(GenerationTime.INSERT) public Date getRegistrationDate() @@ -134,6 +137,7 @@ public final class ProjectPE extends AttachmentHolderPE implements Comparable<Pr this.registrator = registrator; } + @OptimisticLock(excluded = true) @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = ColumnNames.PERSON_MODIFIER_COLUMN) public PersonPE getModifier() @@ -168,6 +172,7 @@ public final class ProjectPE extends AttachmentHolderPE implements Comparable<Pr return space; } + @OptimisticLock(excluded = true) @OneToMany(fetch = FetchType.LAZY, mappedBy = "projectInternal") @ContainedIn private List<ExperimentPE> getExperimentsInternal() @@ -374,7 +379,7 @@ public final class ProjectPE extends AttachmentHolderPE implements Comparable<Pr return projectIdentifier.toString(); } - @Version + @OptimisticLock(excluded = true) @Column(name = ColumnNames.MODIFICATION_TIMESTAMP_COLUMN, nullable = false) public Date getModificationDate() { @@ -386,4 +391,16 @@ public final class ProjectPE extends AttachmentHolderPE implements Comparable<Pr this.modificationDate = versionDate; } + @Version + @Column(name = ColumnNames.VERSION_COLUMN, nullable = false) + public int getVersion() + { + return version; + } + + public void setVersion(int version) + { + this.version = version; + } + } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ProjectTranslator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ProjectTranslator.java index a3143301c5f699a67b09875ec0cb121e6bbdd832..d96a023420c526710aabe5d53fe640ff0ac4efa0 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ProjectTranslator.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/translator/ProjectTranslator.java @@ -61,6 +61,7 @@ public final class ProjectTranslator result.setPermId(project.getPermId()); result.setModifier(PersonTranslator.translate(project.getModifier())); result.setModificationDate(project.getModificationDate()); + result.setVersion(project.getVersion()); result.setCode(project.getCode()); result.setDescription(project.getDescription()); result.setSpace(SpaceTranslator.translate(project.getSpace())); diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java index 6c862130bae367aa4d198c09f916bb179703eea8..c8795a3f949c4f0088fc538eb3d55d25d8621337 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBOTest.java @@ -38,6 +38,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.ManagerTestTool; import ch.systemsx.cisd.openbis.generic.shared.CommonTestUtils; import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment; import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE; import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE; @@ -47,6 +48,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE; import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPropertyPE; import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentTypePE; import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentTypePropertyTypePE; +import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentUpdatesDTO; import ch.systemsx.cisd.openbis.generic.shared.dto.IAuthSession; import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE; import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE; @@ -448,29 +450,49 @@ public final class ExperimentBOTest extends AbstractBOTest { ExperimentIdentifier identifier = CommonTestUtils.createExperimentIdentifier(); - ExperimentPE exp = CommonTestUtils.createExperiment(identifier); - - SpacePE group = CommonTestUtils.createSpace(identifier); + ExperimentTypePE experimentType = CommonTestUtils.createExperimentType(); + final ExperimentPE exp = CommonTestUtils.createExperiment(identifier); + exp.setExperimentType(experimentType); + SpacePE space = CommonTestUtils.createSpace(identifier); SamplePE assignedSample = createSampleWithCode("assignedSample"); - assignedSample.setSpace(group); + assignedSample.setSpace(space); exp.setSamples(Arrays.asList(assignedSample)); - - prepareLoadExperimentByIdentifier(identifier, exp); - ExperimentBO expBO = loadExperiment(identifier, exp); - + // prepareLoadExperimentByIdentifier(identifier, exp); final ProjectIdentifier newProjectIdentifier = - new ProjectIdentifier(identifier.getDatabaseInstanceCode(), "anotherGroup", + new ProjectIdentifier(identifier.getDatabaseInstanceCode(), "anotherSpace", "anotherProject"); final ProjectPE newProject = CommonTestUtils.createProject(newProjectIdentifier); + ExperimentUpdatesDTO updates = new ExperimentUpdatesDTO(); + updates.setExperimentId(new TechId(exp)); + updates.setVersion(exp.getModificationDate()); + updates.setProjectIdentifier(newProjectIdentifier); + updates.setProperties(Collections.<IEntityProperty> emptyList()); + updates.setAttachments(Collections.<NewAttachment> emptyList()); + prepareAnyDaoCreation(); + context.checking(new Expectations() + { + { + one(experimentDAO).tryGetByTechId(new TechId(exp.getId()), + ExperimentBO.PROPERTY_TYPES); + will(returnValue(exp)); + + one(entityTypeDAO).listEntityTypes(); + will(returnValue(Arrays.asList(exp.getExperimentType()))); + + allowing(entityPropertyTypeDAO) + .listEntityPropertyTypes(exp.getExperimentType()); + one(relationshipService).assignExperimentToProject( + ManagerTestTool.EXAMPLE_SESSION, exp, newProject); + } + }); prepareTryFindProject(newProjectIdentifier, newProject); + ExperimentBO expBO = createExperimentBO(); + expBO.update(updates); + assertFalse(newProject.equals(exp.getProject())); assertFalse(newProject.getSpace().equals(assignedSample.getSpace())); - expBO.updateProject(newProjectIdentifier); - - assertEquals(newProject, exp.getProject()); - assertEquals(newProject.getSpace(), assignedSample.getSpace()); } @Test diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/util/TimeIntervalChecker.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/util/TimeIntervalChecker.java new file mode 100644 index 0000000000000000000000000000000000000000..8c26dfcb61f26101720fff65624853de0062f948 --- /dev/null +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/util/TimeIntervalChecker.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.server.util; + +import java.util.Date; + +import org.testng.AssertJUnit; + +/** + * Helper class to check that a time stamp is between now and a time in the past when an instance of + * this class has been created. + * <p> + * This class is useful in tests where some productive code creates a time stamp which should be + * checked. + * + * @author Franz-Josef Elmer + */ +public class TimeIntervalChecker extends AssertJUnit +{ + private Date notBeforeDate; + + /** + * Creates an instance for now. + */ + public TimeIntervalChecker() + { + this(0); + } + + /** + * Creates an instance for now minus specified shift in seconds. + */ + public TimeIntervalChecker(long shiftInSeconds) + { + notBeforeDate = new Date(System.currentTimeMillis() - shiftInSeconds * 1000); + } + + /** + * Asserts that the specified date is after the time stamp of creation of this instance and + * before now. + */ + public void assertDateInInterval(Date date) + { + assertTrue("Actual date [" + date + "] is before notBeforeDate [" + notBeforeDate + "].", + notBeforeDate.getTime() <= date.getTime()); + Date now = new Date(); + assertTrue("Actual date [" + date + "] is after now [" + now + "].", + now.getTime() >= date.getTime()); + + } +} diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/CommonTestUtils.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/CommonTestUtils.java index eb411e6add9c5cff5b5cf6238c8815f14a372cf1..dca8068f337b4ec050e76ac951b770a66bd68d10 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/CommonTestUtils.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/CommonTestUtils.java @@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.generic.shared; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.Date; import java.util.List; import org.apache.commons.io.FileUtils; @@ -345,10 +346,12 @@ public class CommonTestUtils final ExperimentPE exp = new ExperimentPE(); final ExperimentTypePE expType = new ExperimentTypePE(); expType.setCode("expType"); + exp.setId(42L); exp.setExperimentType(expType); exp.setCode(ei.getExperimentCode()); exp.setProject(createProject(new ProjectIdentifier(ei.getDatabaseInstanceCode(), ei .getSpaceCode(), ei.getProjectCode()))); + exp.setModificationDate(new Date(4711L)); return exp; } diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/dto/builders/AtomicEntityOperationDetailsBuilder.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/dto/builders/AtomicEntityOperationDetailsBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..88d60832781c987c51cea55d74fcb25c95609acb --- /dev/null +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/dto/builders/AtomicEntityOperationDetailsBuilder.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.shared.dto.builders; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace; +import ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationDetails; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetBatchUpdatesDTO; +import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentUpdatesDTO; +import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialUpdateDTO; +import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData; +import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO; + +/** + * @author Franz-Josef Elmer + */ +public class AtomicEntityOperationDetailsBuilder +{ + private final List<ExperimentUpdatesDTO> experimentUpdates = + new ArrayList<ExperimentUpdatesDTO>(); + + private final List<NewSpace> spaceRegistrations = new ArrayList<NewSpace>(); + + private final List<NewProject> projectRegistrations = new ArrayList<NewProject>(); + + private final List<NewExperiment> experimentRegistrations = new ArrayList<NewExperiment>(); + + private final List<SampleUpdatesDTO> sampleUpdates = new ArrayList<SampleUpdatesDTO>(); + + private final List<NewSample> sampleRegistrations = new ArrayList<NewSample>(); + + private final Map<String /* material type */, List<NewMaterial>> materialRegistrations = + new HashMap<String, List<NewMaterial>>(); + + private final List<MaterialUpdateDTO> materialUpdates = new ArrayList<MaterialUpdateDTO>(); + + private final List<? extends NewExternalData> dataSetRegistrations = + new ArrayList<NewExternalData>(); + + private final List<DataSetBatchUpdatesDTO> dataSetUpdates = + new ArrayList<DataSetBatchUpdatesDTO>(); + + private TechId registrationIdOrNull; + + private String userIdOrNull; + + private Integer batchSizeOrNull; + + public AtomicEntityOperationDetailsBuilder user(String userId) + { + userIdOrNull = userId; + return this; + } + + public AtomicEntityOperationDetailsBuilder batchSize(int size) + { + batchSizeOrNull = size; + return this; + } + + public AtomicEntityOperationDetailsBuilder addNewExperiment(NewExperiment newExperiment) + { + experimentRegistrations.add(newExperiment); + return this; + } + + public AtomicEntityOperationDetailsBuilder addNewSample(NewSample newSample) + { + sampleRegistrations.add(newSample); + return this; + } + + public AtomicEntityOperationDetails getDetails() + { + return new AtomicEntityOperationDetails(registrationIdOrNull, userIdOrNull, + spaceRegistrations, projectRegistrations, experimentRegistrations, + experimentUpdates, sampleUpdates, sampleRegistrations, materialRegistrations, + materialUpdates, dataSetRegistrations, dataSetUpdates, batchSizeOrNull); + } +} diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/PersistentSystemTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/PersistentSystemTestCase.java new file mode 100644 index 0000000000000000000000000000000000000000..e22470fe2bf03b53b7aacee181c343b580b13bd1 --- /dev/null +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/PersistentSystemTestCase.java @@ -0,0 +1,262 @@ +/* + * Copyright 2009 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.systemtest; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; +import org.testng.AssertJUnit; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeSuite; + +import ch.systemsx.cisd.common.servlet.SpringRequestContextProvider; +import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientService; +import ch.systemsx.cisd.openbis.generic.client.web.client.dto.SessionContext; +import ch.systemsx.cisd.openbis.generic.client.web.server.UploadedFilesBean; +import ch.systemsx.cisd.openbis.generic.server.ICommonServerForInternalUse; +import ch.systemsx.cisd.openbis.generic.server.util.TestInitializer; +import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DisplaySettings; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.PropertyBuilder; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier; +import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.IGenericClientService; +import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer; + +/** + * Abstract super class of head-less system tests which makes database changes persistent. Test + * classes extending this test case class are responsible for cleaning up the database. + * + * @author Franz-Josef Elmer + */ +@ContextConfiguration(locations = "classpath:applicationContext.xml") +public abstract class PersistentSystemTestCase extends AbstractTestNGSpringContextTests +{ + protected static final String SESSION_KEY = "session-key"; + + protected ICommonServerForInternalUse commonServer; + + protected IGenericServer genericServer; + + protected ICommonClientService commonClientService; + + protected IGenericClientService genericClientService; + + protected IETLLIMSService etlService; + + protected MockHttpServletRequest request; + + protected String systemSessionToken; + + @BeforeSuite + public void beforeSuite() + { + TestInitializer.init(); + } + + @BeforeClass + public void loginAsSystem() + { + systemSessionToken = commonServer.tryToAuthenticateAsSystem().getSessionToken(); + } + + /** + * Sets a {@link MockHttpServletRequest} for the specified context provider + */ + @Autowired + public final void setRequestContextProvider(final SpringRequestContextProvider contextProvider) + { + request = new MockHttpServletRequest(); + contextProvider.setRequest(request); + } + + /** + * Sets <code>commonServer</code>. + * <p> + * Will be automatically dependency injected by type. + * </p> + */ + @Autowired + public final void setCommonServer(final ICommonServerForInternalUse commonServer) + { + this.commonServer = commonServer; + } + + /** + * Sets <code>genericServer</code>. + * <p> + * Will be automatically dependency injected by type. + * </p> + */ + @Autowired + public final void setGenericServer(final IGenericServer genericServer) + { + this.genericServer = genericServer; + } + + /** + * Sets <code>commonClientService</code>. + * <p> + * Will be automatically dependency injected by type. + * </p> + */ + @Autowired + public final void setCommonClientService(final ICommonClientService commonClientService) + { + this.commonClientService = commonClientService; + } + + /** + * Sets <code>genericClientService</code>. + * <p> + * Will be automatically dependency injected by type. + * </p> + */ + @Autowired + public final void setGenericClientService(final IGenericClientService genericClientService) + { + this.genericClientService = genericClientService; + } + + @Autowired + public void setETLService(IETLLIMSService etlService) + { + this.etlService = etlService; + + } + + protected SessionContext logIntoCommonClientService() + { + SessionContext context = commonClientService.tryToLogin("test", "a"); + AssertJUnit.assertNotNull(context); + return context; + } + + protected void logOutFromCommonClientService() + { + commonClientService.logout(new DisplaySettings(), false); + } + + protected void sleep(long millis) + { + try + { + Thread.sleep(millis); + } catch (InterruptedException ex) + { + ex.printStackTrace(); + } + } + + public final class NewSampleBuilder + { + private NewSample sample = new NewSample(); + + private List<IEntityProperty> propertis = new ArrayList<IEntityProperty>(); + + public NewSampleBuilder(String identifier) + { + sample.setIdentifier(identifier); + } + + public NewSampleBuilder type(String type) + { + SampleType sampleType = new SampleType(); + sampleType.setCode(type); + sample.setSampleType(sampleType); + return this; + } + + public NewSampleBuilder experiment(String identifier) + { + sample.setExperimentIdentifier(identifier); + return this; + } + + public NewSampleBuilder parents(String... parentIdentifiers) + { + sample.setParentsOrNull(parentIdentifiers); + return this; + } + + public NewSampleBuilder property(String key, String value) + { + propertis.add(new PropertyBuilder(key).value(value).getProperty()); + return this; + } + + public void register() + { + sample.setProperties(propertis.toArray(new IEntityProperty[propertis.size()])); + genericClientService.registerSample(SESSION_KEY, sample); + } + } + + /** + * Register a person with specified user ID. + * + * @return userID + */ + protected String registerPerson(String userID) + { + commonServer.registerPerson(systemSessionToken, userID); + return userID; + } + + protected void assignInstanceRole(String userID, RoleCode roleCode) + { + commonServer.registerInstanceRole(systemSessionToken, roleCode, + Grantee.createPerson(userID)); + } + + protected void assignSpaceRole(String userID, RoleCode roleCode, SpaceIdentifier spaceIdentifier) + { + commonServer.registerSpaceRole(systemSessionToken, roleCode, spaceIdentifier, + Grantee.createPerson(userID)); + } + + /** + * Authenticates as specified user. + * + * @return session token + */ + protected String authenticateAs(String user) + { + return commonServer.tryToAuthenticate(user, "password").getSessionToken(); + } + + protected void uploadFile(String fileName, String fileContent) + { + UploadedFilesBean bean = new UploadedFilesBean(); + bean.addMultipartFile(new MockMultipartFile(fileName, fileName, null, fileContent + .getBytes())); + HttpSession session = request.getSession(); + session.setAttribute(SESSION_KEY, bean); + } + +} diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/base/builder/ProjectUpdateBuilder.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/base/builder/ProjectUpdateBuilder.java index ef7b365cd667e686cbe1d817218524a9340b659a..b7bf969f2bcbc5097a58d0a8aec727d10c4f9313 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/base/builder/ProjectUpdateBuilder.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/base/builder/ProjectUpdateBuilder.java @@ -39,7 +39,7 @@ public class ProjectUpdateBuilder extends UpdateBuilder<ProjectUpdatesDTO> updates.setAttachments(new ArrayList<NewAttachment>()); updates.setDescription(project.getDescription()); updates.setTechId(new TechId(project.getId())); - updates.setVersion(project.getModificationDate()); + updates.setVersion(project.getVersion()); } public ProjectUpdateBuilder toSpace(Space space) diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/optimistic_locking/OptimisticLockingTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/optimistic_locking/OptimisticLockingTestCase.java new file mode 100644 index 0000000000000000000000000000000000000000..5f5bfa2df58428af3a3aaf6501a1e3f3d9cbacd5 --- /dev/null +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/optimistic_locking/OptimisticLockingTestCase.java @@ -0,0 +1,197 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.systemtest.optimistic_locking; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import ch.systemsx.cisd.openbis.generic.shared.basic.ICodeHolder; +import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Deletion; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DeletionType; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.ExperimentTypeBuilder; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.PropertyBuilder; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.DatabaseInstanceIdentifier; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifierFactory; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifierFactory; +import ch.systemsx.cisd.openbis.systemtest.PersistentSystemTestCase; + +/** + * @author Franz-Josef Elmer + */ +public class OptimisticLockingTestCase extends PersistentSystemTestCase +{ + protected static final String USER_ID = "optimist"; + + protected static final String SPACE_1 = "OPTIMISTIC_LOCKING_1"; + + protected static final String SPACE_2 = "OPTIMISTIC_LOCKING_2"; + + protected static final String EXPERIMENT_TYPE_CODE = "SIRNA_HCS"; + + protected Space space1; + + protected Space space2; + + protected Project project1; + + protected Project project2; + + @BeforeMethod + public void createSpacesAndProjects() + { + space1 = findOrCreateSpace(SPACE_1); + space2 = findOrCreateSpace(SPACE_2); + project1 = findOrCreateProject("/" + SPACE_1 + "/P1"); + project2 = findOrCreateProject("/" + SPACE_2 + "/P2"); + createInstanceAdmin(USER_ID); + } + + @AfterMethod + public void deleteSpaces() + { + deleteSpace(space1); + deleteSpace(space2); + } + + private void deleteSpace(Space space) + { + List<Experiment> experiments = + commonServer.listExperiments(systemSessionToken, + new ExperimentTypeBuilder().code(EXPERIMENT_TYPE_CODE).getExperimentType(), + new SpaceIdentifierFactory(space.getIdentifier()).createIdentifier()); + commonServer.deleteExperiments(systemSessionToken, TechId.createList(experiments), + "cleanup", DeletionType.TRASH); + List<Deletion> deletions = commonServer.listDeletions(systemSessionToken, false); + commonServer.deletePermanently(systemSessionToken, TechId.createList(deletions)); + List<Project> projects = commonServer.listProjects(systemSessionToken); + List<TechId> projectIds = new ArrayList<TechId>(); + for (Project project : projects) + { + if (project.getSpace().getCode().equals(space.getCode())) + { + projectIds.add(new TechId(project)); + } + } + commonServer.deleteProjects(systemSessionToken, projectIds, "cleanup"); + commonServer.deleteSpaces(systemSessionToken, Arrays.asList(new TechId(space.getId())), + "cleanup"); + } + + private void createInstanceAdmin(String userId) + { + List<Person> persons = commonServer.listPersons(systemSessionToken); + for (Person person : persons) + { + if (person.getUserId().equals(userId)) + { + return; + } + } + commonServer.registerPerson(systemSessionToken, userId); + commonServer.registerInstanceRole(systemSessionToken, RoleCode.ADMIN, + Grantee.createPerson(userId)); + } + + protected Project findOrCreateProject(String projectIdentifier) + { + Project project = tryToFindProject(projectIdentifier); + if (project != null) + { + return project; + } + commonServer.registerProject(systemSessionToken, new ProjectIdentifierFactory( + projectIdentifier).createIdentifier(), "A test project", null, Collections + .<NewAttachment> emptyList()); + return tryToFindProject(projectIdentifier); + } + + protected Project tryToFindProject(String projectIdentifier) + { + List<Project> projects = commonServer.listProjects(systemSessionToken); + for (Project project : projects) + { + if (project.getIdentifier().equals(projectIdentifier)) + { + return project; + } + } + return null; + } + + protected Space findOrCreateSpace(String spaceCode) + { + Space space = tryToFindSpace(spaceCode); + if (space != null) + { + return space; + } + commonServer.registerSpace(systemSessionToken, spaceCode, "A test space"); + return tryToFindSpace(spaceCode); + } + + protected Space tryToFindSpace(String spaceCode) + { + DatabaseInstanceIdentifier identifier = new DatabaseInstanceIdentifier(null); + List<Space> spaces = commonServer.listSpaces(systemSessionToken, identifier); + for (Space space : spaces) + { + if (space.getCode().equals(spaceCode)) + { + return space; + } + } + return null; + } + + protected NewExperiment experiment(int number) + { + NewExperiment experiment = + new NewExperiment(project1.getIdentifier() + "/OLT-E" + number, + EXPERIMENT_TYPE_CODE); + experiment.setAttachments(Collections.<NewAttachment> emptyList()); + experiment.setProperties(new IEntityProperty[] + { new PropertyBuilder("DESCRIPTION").value("hello " + number).getProperty() }); + return experiment; + } + + protected List<String> extractCodes(List<? extends ICodeHolder> codeHolders) + { + List<String> result = new ArrayList<String>(); + for (ICodeHolder codeHolder : codeHolders) + { + result.add(codeHolder.getCode()); + } + Collections.sort(result); + return result; + } + +} diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/optimistic_locking/ProjectOptimisticLockingTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/optimistic_locking/ProjectOptimisticLockingTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7739d790a0b80063e7b9cd590e3a1c42b0bb654e --- /dev/null +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/optimistic_locking/ProjectOptimisticLockingTest.java @@ -0,0 +1,262 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.systemtest.optimistic_locking; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.fail; + +import java.util.Collections; +import java.util.List; + +import org.apache.log4j.Logger; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.concurrent.MessageChannel; +import ch.systemsx.cisd.common.concurrent.MessageChannelBuilder; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.openbis.common.conversation.context.ServiceConversationsThreadContext; +import ch.systemsx.cisd.openbis.common.conversation.progress.IServiceConversationProgressListener; +import ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException; +import ch.systemsx.cisd.openbis.generic.server.util.TimeIntervalChecker; +import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ProjectUpdates; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.ExperimentTypeBuilder; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.PropertyBuilder; +import ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationDetails; +import ch.systemsx.cisd.openbis.generic.shared.dto.builders.AtomicEntityOperationDetailsBuilder; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifierFactory; + +/** + * @author Franz-Josef Elmer + */ +public class ProjectOptimisticLockingTest extends OptimisticLockingTestCase +{ + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + ProjectOptimisticLockingTest.class); + + private static final String REGISTERED = "registered"; + + private static final String FIRST_REGISTERED = "First registered"; + + private static final String CREATE_EXPERIMENTS_PHASE = "createExperiments"; + + @Test + public void testCreateProject() + { + Project project = new Project(); + project.setCode("POLT-1"); + String identifier = "/" + space1.getCode() + "/POLT-1"; + project.setIdentifier(identifier); + project.setDescription("ProjectOptimisticLockingTest test"); + project.setSpace(space1); + logIntoCommonClientService(); + TimeIntervalChecker timeIntervalChecker = new TimeIntervalChecker(1); + + commonClientService.registerProject(SESSION_KEY, project); + + Project p = + commonServer + .getProjectInfo(systemSessionToken, createProjectIdentifier(identifier)); + assertEquals(project.getDescription(), p.getDescription()); + assertEquals("test", p.getRegistrator().getUserId()); + assertEquals("test", p.getModifier().getUserId()); + timeIntervalChecker.assertDateInInterval(p.getRegistrationDate()); + timeIntervalChecker.assertDateInInterval(p.getModificationDate()); + } + + private ProjectIdentifier createProjectIdentifier(String identifier) + { + return new ProjectIdentifierFactory(identifier).createIdentifier(); + } + + @Test + public void testUpdateProjectAndCheckModificationDateAndModifier() + { + ProjectIdentifier projectIdentifier = createProjectIdentifier(project1.getIdentifier()); + Project p = commonServer.getProjectInfo(systemSessionToken, projectIdentifier); + ProjectUpdates updates = new ProjectUpdates(); + updates.setVersion(p.getVersion()); + updates.setTechId(new TechId(p)); + updates.setDescription(p.getDescription() + " 2"); + updates.setAttachments(Collections.<NewAttachment> emptyList()); + updates.setAttachmentSessionKey(SESSION_KEY); + logIntoCommonClientService(); + TimeIntervalChecker timeIntervalChecker = new TimeIntervalChecker(1); + + commonClientService.updateProject(updates); + + p = commonServer.getProjectInfo(systemSessionToken, projectIdentifier); + assertEquals(project1.getDescription() + " 2", p.getDescription()); + assertEquals("system", p.getRegistrator().getUserId()); + assertEquals("test", p.getModifier().getUserId()); + timeIntervalChecker.assertDateInInterval(p.getModificationDate()); + + } + + @Test + public void testUpdateProjectWithOldVersion() + { + ProjectIdentifier projectIdentifier = createProjectIdentifier(project1.getIdentifier()); + Project currentProject = commonServer.getProjectInfo(systemSessionToken, projectIdentifier); + ProjectUpdates updates = new ProjectUpdates(); + updates.setVersion(currentProject.getVersion()); + updates.setTechId(new TechId(currentProject)); + updates.setDescription(currentProject.getDescription() + " 1"); + updates.setAttachments(Collections.<NewAttachment> emptyList()); + updates.setAttachmentSessionKey(SESSION_KEY); + logIntoCommonClientService(); + commonClientService.updateProject(updates); + + try + { + commonClientService.updateProject(updates); + fail("UserFailureException expected"); + } catch (UserFailureException ex) + { + assertEquals("Project has been modified in the meantime. Reopen tab to be able " + + "to continue with refreshed data.", ex.getMessage()); + } + } + + @Test + public void testRegisterExperimentAndCheckModificationDateAndModifierOfProject() + { + String sessionToken = logIntoCommonClientService().getSessionID(); + NewExperiment experiment = + new NewExperiment(project1.getIdentifier() + "/POLT-1", EXPERIMENT_TYPE_CODE); + experiment.setAttachments(Collections.<NewAttachment> emptyList()); + experiment.setProperties(new IEntityProperty[] + { new PropertyBuilder("DESCRIPTION").value("hello").getProperty() }); + assertEquals("system", project1.getModifier().getUserId()); + TimeIntervalChecker timeIntervalChecker = new TimeIntervalChecker(1); + + genericServer.registerExperiment(sessionToken, experiment, + Collections.<NewAttachment> emptyList()); + + checkModifierAndModificationDateOfProject1(timeIntervalChecker); + } + + @Test + public void testRegisterExperiments() + { + assertEquals("system", project1.getModifier().getUserId()); + AtomicEntityOperationDetailsBuilder builder = new AtomicEntityOperationDetailsBuilder(); + builder.user(USER_ID); + builder.addNewExperiment(experiment(1)).addNewExperiment(experiment(2)); + TimeIntervalChecker timeIntervalChecker = new TimeIntervalChecker(1); + + etlService.performEntityOperations(systemSessionToken, builder.getDetails()); + + List<Experiment> experiments = + commonServer.listExperiments(systemSessionToken, + new ExperimentTypeBuilder().code(EXPERIMENT_TYPE_CODE).getExperimentType(), + createProjectIdentifier(project1.getIdentifier())); + assertEquals("[OLT-E1, OLT-E2]", extractCodes(experiments).toString()); + checkModifierAndModificationDateOfProject1(timeIntervalChecker, USER_ID); + } + + @Test + public void testRegisterExperiments2() + { + assertEquals("system", project1.getModifier().getUserId()); + final StringBuilder stringBuilder = new StringBuilder(); + final MessageChannel messageChannelMain = + new MessageChannelBuilder(10000).name("main").logger(operationLog).getChannel(); + final MessageChannel messageChannelSecond = + new MessageChannelBuilder(10000).name("second").logger(operationLog).getChannel(); + final IServiceConversationProgressListener listener = + new IServiceConversationProgressListener() + { + @Override + public void update(String phaseName, int totalItemsToProcess, + int numItemsProcessed) + { + stringBuilder.append(phaseName).append(" ").append(numItemsProcessed) + .append("/").append(totalItemsToProcess).append("\n"); + if (phaseName.equals(CREATE_EXPERIMENTS_PHASE) + && numItemsProcessed == 1 && totalItemsToProcess == 2) + { + messageChannelMain.send(FIRST_REGISTERED); + } + } + + @Override + public void close() + { + } + }; + TimeIntervalChecker timeIntervalChecker = new TimeIntervalChecker(1); + + new Thread(new Runnable() + { + @Override + public void run() + { + NewExperiment experiment3 = experiment(3); + String sessionToken = + genericServer.tryToAuthenticate("test", "a").getSessionToken(); + messageChannelMain.assertNextMessage(FIRST_REGISTERED); + genericServer.registerExperiment(sessionToken, experiment3, + Collections.<NewAttachment> emptyList()); + messageChannelSecond.send(REGISTERED); + } + }).start(); + + ServiceConversationsThreadContext.setProgressListener(listener); + AtomicEntityOperationDetailsBuilder builder = new AtomicEntityOperationDetailsBuilder(); + builder.user(USER_ID); + builder.addNewExperiment(experiment(1)).addNewExperiment(experiment(2)); + AtomicEntityOperationDetails details = builder.getDetails(); + + etlService.performEntityOperations(systemSessionToken, details); + + messageChannelSecond.assertNextMessage(REGISTERED); + + List<Experiment> experiments = + commonServer.listExperiments(systemSessionToken, + new ExperimentTypeBuilder().code(EXPERIMENT_TYPE_CODE).getExperimentType(), + createProjectIdentifier(project1.getIdentifier())); + assertEquals("[OLT-E1, OLT-E2, OLT-E3]", extractCodes(experiments).toString()); + checkModifierAndModificationDateOfProject1(timeIntervalChecker); + assertEquals("authorize 1/2\n" + "authorize 2/2\n" + "createExperiments 1/2\n" + + "createExperiments 2/2\n", stringBuilder.toString()); + } + + private void checkModifierAndModificationDateOfProject1(TimeIntervalChecker timeIntervalChecker) + { + checkModifierAndModificationDateOfProject1(timeIntervalChecker, "test"); + } + + private void checkModifierAndModificationDateOfProject1( + TimeIntervalChecker timeIntervalChecker, String modifier) + { + ProjectIdentifier projectIdentifier = createProjectIdentifier(project1.getIdentifier()); + Project p = commonServer.getProjectInfo(systemSessionToken, projectIdentifier); + assertEquals("system", p.getRegistrator().getUserId()); + assertEquals(project1.getRegistrationDate(), p.getRegistrationDate()); + assertEquals(modifier, p.getModifier().getUserId()); + timeIntervalChecker.assertDateInInterval(p.getModificationDate()); + } + +}