From 15f64e46d102ae3ee0e2c5d824d23c24c4fbf2a7 Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Mon, 2 Nov 2015 13:51:52 +0000
Subject: [PATCH] SSDM-2706 : V3 AS API - create data sets (only metadata) -
 unit tests

SVN: 34962
---
 .../dataset/CreateDataSetExecutor.java        |   4 +
 ...ifyDataSetSampleAndExperimentExecutor.java |  28 ++
 .../dataset/SetDataSetSampleExecutor.java     |  50 --
 .../dataset/VerifyDataSetExecutor.java        |   4 +
 ...ifyDataSetSampleAndExperimentExecutor.java | 130 ++++++
 .../entity/AbstractCreateEntityExecutor.java  |   4 +-
 openbis/source/java/service.properties        |   2 +-
 .../systemtest/api/v3/CreateDataSetTest.java  | 441 ++++++++++++++++++
 .../systemtest/api/v3/CreateSampleTest.java   |  24 +-
 .../sql/postgresql/149/014=data_set_types.tsv |   1 +
 10 files changed, 623 insertions(+), 65 deletions(-)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/IVerifyDataSetSampleAndExperimentExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetSampleAndExperimentExecutor.java

diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/CreateDataSetExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/CreateDataSetExecutor.java
index f29740a75f9..16a766b65f6 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/CreateDataSetExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/CreateDataSetExecutor.java
@@ -105,6 +105,10 @@ public class CreateDataSetExecutor extends AbstractCreateEntityExecutor<DataSetC
         {
             throw new UserFailureException("Type id cannot be null.");
         }
+        if (creation.getExperimentId() == null && creation.getSampleId() == null)
+        {
+            throw new UserFailureException("Experiment id and sample id cannot be both null.");
+        }
     }
 
     @Override
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/IVerifyDataSetSampleAndExperimentExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/IVerifyDataSetSampleAndExperimentExecutor.java
new file mode 100644
index 00000000000..26dfb39ea5a
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/IVerifyDataSetSampleAndExperimentExecutor.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.executor.dataset;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.entity.IVerifyEntityRelationsExecutor;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
+
+/**
+ * @author pkupczyk
+ */
+public interface IVerifyDataSetSampleAndExperimentExecutor extends IVerifyEntityRelationsExecutor<DataPE>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/SetDataSetSampleExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/SetDataSetSampleExecutor.java
index 4de346e9243..2b8da7690b7 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/SetDataSetSampleExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/SetDataSetSampleExecutor.java
@@ -16,22 +16,13 @@
 
 package ch.ethz.sis.openbis.generic.server.api.v3.executor.dataset;
 
-import java.util.Properties;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.Resource;
-
 import org.springframework.stereotype.Component;
 
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.IOperationContext;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.entity.AbstractSetEntitySampleRelationExecutor;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.dataset.DataSetCreation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.ISampleId;
-import ch.systemsx.cisd.common.exceptions.UserFailureException;
-import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
-import ch.systemsx.cisd.openbis.generic.server.business.bo.util.DataSetTypeWithoutExperimentChecker;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.util.RelationshipUtils;
 
@@ -43,17 +34,6 @@ public class SetDataSetSampleExecutor extends AbstractSetEntitySampleRelationExe
         ISetDataSetSampleExecutor
 {
 
-    @Resource(name = ExposablePropertyPlaceholderConfigurer.PROPERTY_CONFIGURER_BEAN_NAME)
-    protected ExposablePropertyPlaceholderConfigurer configurer;
-
-    private DataSetTypeWithoutExperimentChecker dataSetTypeChecker;
-
-    @PostConstruct
-    public void init() throws Exception
-    {
-        dataSetTypeChecker = new DataSetTypeWithoutExperimentChecker(configurer == null ? new Properties() : configurer.getResolvedProps());
-    }
-
     @Override
     protected ISampleId getRelatedId(DataSetCreation creation)
     {
@@ -65,39 +45,9 @@ public class SetDataSetSampleExecutor extends AbstractSetEntitySampleRelationExe
     {
         if (related != null)
         {
-            assertAllowedSampleForDataSet(entity, related);
             RelationshipUtils.setSampleForDataSet(entity, related, context.getSession());
             RelationshipUtils.setExperimentForDataSet(entity, related.getExperiment(), context.getSession());
         }
     }
 
-    private void assertAllowedSampleForDataSet(DataPE dataSet, SamplePE sample)
-    {
-        ExperimentPE experiment = sample.getExperiment();
-
-        if (experiment == null)
-        {
-            if (dataSetTypeChecker.isDataSetTypeWithoutExperiment(dataSet.getDataSetType().getCode()))
-            {
-                if (sample.getSpace() == null)
-                {
-                    throw new UserFailureException("Data set can not be registered because sample '"
-                            + sample.getSampleIdentifier() + "' is a shared sample.");
-                } else if (sample.getDeletion() != null)
-                {
-                    throw new UserFailureException("Data set can not be registered because sample '"
-                            + sample.getSampleIdentifier() + "' is in trash.");
-                }
-            } else
-            {
-                throw new UserFailureException("Data set can not be registered because no experiment "
-                        + "found for sample '" + sample.getSampleIdentifier() + "'.");
-            }
-        } else if (experiment.getDeletion() != null)
-        {
-            throw new UserFailureException("Data set can not be registered because experiment '"
-                    + experiment.getIdentifier() + "' is in trash.");
-        }
-    }
-
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetExecutor.java
index 214acaeb7ff..e0f81e22dde 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetExecutor.java
@@ -32,6 +32,9 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
 public class VerifyDataSetExecutor implements IVerifyDataSetExecutor
 {
 
+    @Autowired
+    private IVerifyDataSetSampleAndExperimentExecutor verifyDataSetSampleAndExperimentExecutor;
+
     @Autowired
     private IVerifyEntityPropertyExecutor verifyEntityPropertyExecutor;
 
@@ -44,6 +47,7 @@ public class VerifyDataSetExecutor implements IVerifyDataSetExecutor
     @Override
     public void verify(IOperationContext context, Collection<DataPE> dataSets)
     {
+        verifyDataSetSampleAndExperimentExecutor.verify(context, dataSets);
         verifyEntityPropertyExecutor.verify(context, dataSets);
         verifyDataSetContainersExecutor.verify(context, dataSets);
         verifyDataSetParentsExecutor.verify(context, dataSets);
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetSampleAndExperimentExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetSampleAndExperimentExecutor.java
new file mode 100644
index 00000000000..24a1f065d12
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/dataset/VerifyDataSetSampleAndExperimentExecutor.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.executor.dataset;
+
+import java.util.Collection;
+import java.util.Properties;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.IOperationContext;
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.util.DataSetTypeWithoutExperimentChecker;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class VerifyDataSetSampleAndExperimentExecutor implements IVerifyDataSetSampleAndExperimentExecutor
+{
+
+    @Resource(name = ExposablePropertyPlaceholderConfigurer.PROPERTY_CONFIGURER_BEAN_NAME)
+    private ExposablePropertyPlaceholderConfigurer configurer;
+
+    private DataSetTypeWithoutExperimentChecker dataSetTypeChecker;
+
+    @PostConstruct
+    public void init() throws Exception
+    {
+        dataSetTypeChecker = new DataSetTypeWithoutExperimentChecker(configurer == null ? new Properties() : configurer.getResolvedProps());
+    }
+
+    @Override
+    public void verify(IOperationContext context, Collection<DataPE> dataSets)
+    {
+        for (DataPE dataSet : dataSets)
+        {
+            verify(context, dataSet);
+        }
+    }
+
+    private void verify(IOperationContext context, DataPE dataSet)
+    {
+        ExperimentPE experiment = dataSet.getExperiment();
+        SamplePE sample = dataSet.tryGetSample();
+
+        verifyExperimentNotInTrash(experiment);
+        verifySampleNotInTrash(sample);
+
+        if (sample == null && experiment == null)
+        {
+            throw new UserFailureException("Data set cannot be registered because it is neither connected to a sample nor to an experiment.");
+        } else if (sample == null && experiment != null)
+        {
+            return;
+        } else if (sample != null && experiment == null)
+        {
+            verifySampleNotShared(sample);
+            verifyExperimentNotNeeded(dataSet);
+        } else if (sample != null && experiment != null)
+        {
+            verifySampleNotShared(sample);
+            verifySampleInExperiment(sample, experiment);
+        }
+    }
+
+    private void verifyExperimentNotInTrash(ExperimentPE experiment)
+    {
+        if (experiment != null && experiment.getDeletion() != null)
+        {
+            throw new UserFailureException("Data set can not be registered because experiment '"
+                    + experiment.getIdentifier() + "' is in trash.");
+        }
+    }
+
+    private void verifyExperimentNotNeeded(DataPE dataSet)
+    {
+        if (false == dataSetTypeChecker.isDataSetTypeWithoutExperiment(dataSet.getDataSetType().getCode()))
+        {
+            throw new UserFailureException("Data set can not be registered because it is not connected to an experiment.");
+        }
+    }
+
+    private void verifySampleNotInTrash(SamplePE sample)
+    {
+        if (sample != null && sample.getDeletion() != null)
+        {
+            throw new UserFailureException("Data set can not be registered because sample '"
+                    + sample.getSampleIdentifier() + "' is in trash.");
+        }
+    }
+
+    private void verifySampleNotShared(SamplePE sample)
+    {
+        if (sample.getSpace() == null)
+        {
+            throw new UserFailureException("Data set can not be registered because sample '"
+                    + sample.getSampleIdentifier() + "' is a shared sample.");
+        }
+    }
+
+    private void verifySampleInExperiment(SamplePE sample, ExperimentPE experiment)
+    {
+        if (false == experiment.equals(sample.getExperiment()))
+        {
+            throw new UserFailureException("Data set can not be registered because it connected to a different experiment than its sample.");
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/entity/AbstractCreateEntityExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/entity/AbstractCreateEntityExecutor.java
index 7fa0089a153..9d90ecce5a1 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/entity/AbstractCreateEntityExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/entity/AbstractCreateEntityExecutor.java
@@ -105,14 +105,14 @@ public abstract class AbstractCreateEntityExecutor<CREATION, PE, PERM_ID> implem
             checkAccess(context, entity);
         }
 
+        save(context, new ArrayList<PE>(batchMap.values()), false);
+
         for (PE entity : entitiesAll.values())
         {
             PERM_ID permId = createPermId(context, entity);
             permIdsAll.add(permId);
         }
 
-        save(context, new ArrayList<PE>(batchMap.values()), false);
-
         daoFactory.setBatchUpdateMode(false);
     }
 
diff --git a/openbis/source/java/service.properties b/openbis/source/java/service.properties
index 91606b1c989..e6fcba68c76 100644
--- a/openbis/source/java/service.properties
+++ b/openbis/source/java/service.properties
@@ -13,7 +13,7 @@ core-plugins-folder = source/core-plugins
 
 project-samples-enabled = false
 
-data-set-types-with-no-experiment-needed = .*
+data-set-types-with-no-experiment-needed = (?!REQUIRES\\_EXPERIMENT).*
 
 # Supported: currently only 'postgresql' is supported
 database.engine = postgresql
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateDataSetTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateDataSetTest.java
index 511a473b9b8..11cc5666318 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateDataSetTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateDataSetTest.java
@@ -22,12 +22,17 @@ import static org.testng.Assert.assertNull;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.dataset.DataSet;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.dataset.DataSetCreation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.dataset.PhysicalDataCreation;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.deletion.experiment.ExperimentDeletionOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.deletion.sample.SampleDeletionOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.experiment.ExperimentCreation;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.sample.SampleCreation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.dataset.DataSetFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.DataSetPermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.FileFormatTypePermId;
@@ -35,8 +40,15 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.IDataSetId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.LocatorTypePermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.datastore.DataStorePermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.entitytype.EntityTypePermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.entitytype.IEntityTypeId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.ExperimentIdentifier;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.ExperimentPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.project.ProjectIdentifier;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SampleIdentifier;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SamplePermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.space.SpacePermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.vocabulary.VocabularyTermCode;
+import ch.systemsx.cisd.common.action.IDelegatedAction;
 
 /**
  * @author pkupczyk
@@ -44,6 +56,393 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.vocabulary.VocabularyTer
 public class CreateDataSetTest extends AbstractDataSetTest
 {
 
+    @Test
+    public void testCreateWithCodeNull()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        final DataSetCreation dataSet = dataSetCreation();
+        dataSet.setCode(null);
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(dataSet));
+                }
+            }, "Code cannot be empty");
+    }
+
+    @Test
+    public void testCreateWithCodeExisting()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation dataSet = dataSetCreation();
+        dataSet.setCode("DATA_SET_WITH_EXISTING_CODE");
+        v3api.createDataSets(sessionToken, Arrays.asList(dataSet));
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(dataSet));
+                }
+            }, "DataSet already exists in the database and needs to be unique");
+    }
+
+    @Test
+    public void testCreateWithCodeIncorrect()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        final DataSetCreation dataSet = dataSetCreation();
+        dataSet.setCode("?!*");
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(dataSet));
+                }
+            }, "Given code '?!*' contains illegal characters (allowed: A-Z, a-z, 0-9 and _, -, .)");
+    }
+
+    @Test
+    public void testCreateWithTypeNull()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation dataSet = dataSetCreation();
+        dataSet.setTypeId(null);
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(dataSet));
+                }
+            }, "Type id cannot be null");
+    }
+
+    @Test
+    public void testCreateWithTypeNonexistent()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final IEntityTypeId typeId = new EntityTypePermId("IDONTEXIST");
+        final DataSetCreation dataSet = dataSetCreation();
+        dataSet.setTypeId(typeId);
+
+        assertObjectNotFoundException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(dataSet));
+                }
+            }, typeId);
+    }
+
+    @Test
+    public void testCreateWithPropertyCodeNonexistent()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setProperty("NONEXISTENT_PROPERTY_CODE", "any value");
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, "Property type with code 'NONEXISTENT_PROPERTY_CODE' does not exist");
+    }
+
+    @Test
+    public void testCreateWithPropertyValueIncorrect()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setTypeId(new EntityTypePermId("HCS_IMAGE"));
+        creation.setProperty("GENDER", "NON_EXISTENT_GENDER");
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, "Vocabulary value 'NON_EXISTENT_GENDER' is not valid. It must exist in 'GENDER' controlled vocabulary");
+    }
+
+    @Test
+    public void testCreateWithPropertyValueMandatoryButNull()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setTypeId(new EntityTypePermId("HCS_IMAGE"));
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, "Value of mandatory property 'COMMENT' not specified");
+    }
+
+    @Test
+    public void testCreateWithExperimentNullAndSampleNull()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setExperimentId(null);
+        creation.setSampleId(null);
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, "Experiment id and sample id cannot be both null.");
+    }
+
+    @Test
+    public void testCreateWithExperimentNotNullAndSampleNull()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setExperimentId(new ExperimentIdentifier("/CISD/NEMO/EXP1"));
+        creation.setSampleId(null);
+
+        DataSetFetchOptions fo = new DataSetFetchOptions();
+        fo.withExperiment();
+        fo.withSample();
+
+        List<DataSetPermId> permIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+        Map<IDataSetId, DataSet> dataSets = v3api.mapDataSets(sessionToken, permIds, fo);
+        DataSet dataSet = dataSets.values().iterator().next();
+
+        assertEquals(dataSet.getCode(), creation.getCode().toUpperCase());
+        assertEquals(dataSet.getExperiment().getIdentifier().getIdentifier(), "/CISD/NEMO/EXP1");
+        assertEquals(dataSet.getSample(), null);
+    }
+
+    @Test
+    public void testCreateWithExperimentNullAndSampleInExperiment()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setExperimentId(null);
+        creation.setSampleId(new SampleIdentifier("/CISD/CP-TEST-1"));
+
+        DataSetFetchOptions fo = new DataSetFetchOptions();
+        fo.withExperiment();
+        fo.withSample();
+
+        List<DataSetPermId> permIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+        Map<IDataSetId, DataSet> dataSets = v3api.mapDataSets(sessionToken, permIds, fo);
+        DataSet dataSet = dataSets.values().iterator().next();
+
+        assertEquals(dataSet.getCode(), creation.getCode().toUpperCase());
+        assertEquals(dataSet.getExperiment().getIdentifier().getIdentifier(), "/CISD/NEMO/EXP-TEST-1");
+        assertEquals(dataSet.getSample().getIdentifier().getIdentifier(), "/CISD/CP-TEST-1");
+    }
+
+    @Test
+    public void testCreateWithExperimentNullAndSampleNotInExperimentWhenTypeAllows()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setTypeId(new EntityTypePermId("UNKNOWN"));
+        creation.setExperimentId(null);
+        creation.setSampleId(new SampleIdentifier("/CISD/3V-125"));
+
+        DataSetFetchOptions fo = new DataSetFetchOptions();
+        fo.withExperiment();
+        fo.withSample();
+
+        List<DataSetPermId> permIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+        Map<IDataSetId, DataSet> dataSets = v3api.mapDataSets(sessionToken, permIds, fo);
+        DataSet dataSet = dataSets.values().iterator().next();
+
+        assertEquals(dataSet.getCode(), creation.getCode().toUpperCase());
+        assertEquals(dataSet.getExperiment(), null);
+        assertEquals(dataSet.getSample().getIdentifier().getIdentifier(), "/CISD/3V-125");
+    }
+
+    @Test
+    public void testCreateWithExperimentNullAndSampleNotInExperimentWhenTypeDoesNotAllow()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setTypeId(new EntityTypePermId("REQUIRES_EXPERIMENT"));
+        creation.setExperimentId(null);
+        creation.setSampleId(new SampleIdentifier("/CISD/3V-125"));
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, "Data set can not be registered because it is not connected to an experiment.");
+    }
+
+    @Test
+    public void testCreateWithExperimentNotNullAndSampleNotNullNotInExperiment()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setTypeId(new EntityTypePermId("UNKNOWN"));
+        creation.setExperimentId(new ExperimentIdentifier("/CISD/NEMO/EXP-TEST-1"));
+        creation.setSampleId(new SampleIdentifier("/CISD/3V-125"));
+
+        DataSetFetchOptions fo = new DataSetFetchOptions();
+        fo.withExperiment();
+        fo.withSample();
+
+        List<DataSetPermId> permIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+        Map<IDataSetId, DataSet> dataSets = v3api.mapDataSets(sessionToken, permIds, fo);
+        DataSet dataSet = dataSets.values().iterator().next();
+
+        assertEquals(dataSet.getCode(), creation.getCode().toUpperCase());
+        assertEquals(dataSet.getExperiment(), null);
+        assertEquals(dataSet.getSample().getIdentifier().getIdentifier(), "/CISD/3V-125");
+    }
+
+    @Test
+    public void testCreateWithExperimentNotNullAndSampleNotNullInSameExperiment()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setTypeId(new EntityTypePermId("UNKNOWN"));
+        creation.setExperimentId(new ExperimentIdentifier("/CISD/NEMO/EXP-TEST-1"));
+        creation.setSampleId(new SampleIdentifier("/CISD/CP-TEST-1"));
+
+        DataSetFetchOptions fo = new DataSetFetchOptions();
+        fo.withExperiment();
+        fo.withSample();
+
+        List<DataSetPermId> permIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+        Map<IDataSetId, DataSet> dataSets = v3api.mapDataSets(sessionToken, permIds, fo);
+        DataSet dataSet = dataSets.values().iterator().next();
+
+        assertEquals(dataSet.getCode(), creation.getCode().toUpperCase());
+        assertEquals(dataSet.getExperiment().getIdentifier().getIdentifier(), "/CISD/NEMO/EXP-TEST-1");
+        assertEquals(dataSet.getSample().getIdentifier().getIdentifier(), "/CISD/CP-TEST-1");
+    }
+
+    @Test
+    public void testCreateWithExperimentNotNullAndSampleNotNullInDifferentExperiment()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setTypeId(new EntityTypePermId("UNKNOWN"));
+        creation.setExperimentId(new ExperimentIdentifier("/CISD/DEFAULT/EXP-REUSE"));
+        creation.setSampleId(new SampleIdentifier("/CISD/CP-TEST-1"));
+
+        DataSetFetchOptions fo = new DataSetFetchOptions();
+        fo.withExperiment();
+        fo.withSample();
+
+        List<DataSetPermId> permIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+        Map<IDataSetId, DataSet> dataSets = v3api.mapDataSets(sessionToken, permIds, fo);
+        DataSet dataSet = dataSets.values().iterator().next();
+
+        assertEquals(dataSet.getCode(), creation.getCode().toUpperCase());
+        assertEquals(dataSet.getExperiment().getIdentifier().getIdentifier(), "/CISD/NEMO/EXP-TEST-1");
+        assertEquals(dataSet.getSample().getIdentifier().getIdentifier(), "/CISD/CP-TEST-1");
+    }
+
+    @Test
+    public void testCreateWithExperimentInTrash()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        List<ExperimentPermId> experimentIds = v3api.createExperiments(sessionToken, Arrays.asList(experimentCreation()));
+        ExperimentDeletionOptions deletion = new ExperimentDeletionOptions();
+        deletion.setReason("testing");
+        v3api.deleteExperiments(sessionToken, experimentIds, deletion);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setExperimentId(experimentIds.get(0));
+        creation.setSampleId(null);
+
+        assertObjectNotFoundException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, experimentIds.get(0));
+    }
+
+    @Test
+    public void testCreateWithSampleInTrash()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sampleCreation()));
+        SampleDeletionOptions deletion = new SampleDeletionOptions();
+        deletion.setReason("testing");
+        v3api.deleteSamples(sessionToken, sampleIds, deletion);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setExperimentId(null);
+        creation.setSampleId(sampleIds.get(0));
+
+        assertObjectNotFoundException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, sampleIds.get(0));
+    }
+
+    @Test
+    public void testCreateWithSampleShared()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final DataSetCreation creation = dataSetCreation();
+        creation.setExperimentId(null);
+        creation.setSampleId(new SamplePermId("200811050947161-652"));
+
+        assertUserFailureException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    v3api.createDataSets(sessionToken, Arrays.asList(creation));
+                }
+            }, "Data set can not be registered because sample '/MP' is a shared sample.");
+    }
+
     @Test
     public void testCreatePhysicalData()
     {
@@ -118,4 +517,46 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getContained().iterator().next().getCode(), "20081105092159188-3");
     }
 
+    private DataSetCreation dataSetCreation()
+    {
+        PhysicalDataCreation physicalCreation = new PhysicalDataCreation();
+        physicalCreation.setLocation("a/b/c");
+        physicalCreation.setFileFormatTypeId(new FileFormatTypePermId("TIFF"));
+        physicalCreation.setLocatorTypeId(new LocatorTypePermId("RELATIVE_LOCATION"));
+        physicalCreation.setStorageFormatId(new VocabularyTermCode("PROPRIETARY"));
+
+        DataSetCreation creation = new DataSetCreation();
+        creation.setCode(UUID.randomUUID().toString());
+        creation.setTypeId(new EntityTypePermId("UNKNOWN"));
+        creation.setExperimentId(new ExperimentIdentifier("/CISD/NEMO/EXP1"));
+        creation.setDataStoreId(new DataStorePermId("STANDARD"));
+        creation.setPhysicalData(physicalCreation);
+
+        return creation;
+    }
+
+    private ExperimentCreation experimentCreation()
+    {
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode(UUID.randomUUID().toString());
+        creation.setTypeId(new EntityTypePermId("SIRNA_HCS"));
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty("DESCRIPTION", "a description");
+        return creation;
+    }
+
+    private SampleCreation sampleCreation()
+    {
+        SampleCreation creation = new SampleCreation();
+        creation.setCode(UUID.randomUUID().toString());
+        creation.setTypeId(new EntityTypePermId("CELL_PLATE"));
+        creation.setSpaceId(new SpacePermId("CISD"));
+        return creation;
+    }
+
+    public static void main(String[] args)
+    {
+        System.out.println("REQUIRES_EXPERIMENT".matches("(?!REQUIRES\\_EXPERIMENT).*"));
+    }
+
 }
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateSampleTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateSampleTest.java
index 8a1ec240b7e..c1fa9cbd308 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateSampleTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateSampleTest.java
@@ -941,22 +941,22 @@ public class CreateSampleTest extends AbstractSampleTest
 
     private SampleCreation sampleCreation(String code)
     {
-        SampleCreation sampleParent = new SampleCreation();
-        sampleParent.setCode(code);
-        sampleParent.setTypeId(new EntityTypePermId("CELL_PLATE"));
-        sampleParent.setSpaceId(new SpacePermId("CISD"));
-        sampleParent.setCreationId(new CreationId("creation " + code));
-        return sampleParent;
+        SampleCreation creation = new SampleCreation();
+        creation.setCode(code);
+        creation.setTypeId(new EntityTypePermId("CELL_PLATE"));
+        creation.setSpaceId(new SpacePermId("CISD"));
+        creation.setCreationId(new CreationId("creation " + code));
+        return creation;
     }
 
     private SampleCreation sampleCreation(String spaceCode, String code)
     {
-        SampleCreation sampleParent = new SampleCreation();
-        sampleParent.setCode(code);
-        sampleParent.setTypeId(new EntityTypePermId("CELL_PLATE"));
-        sampleParent.setSpaceId(new SpacePermId(spaceCode));
-        sampleParent.setCreationId(new CreationId("creation " + code));
-        return sampleParent;
+        SampleCreation creation = new SampleCreation();
+        creation.setCode(code);
+        creation.setTypeId(new EntityTypePermId("CELL_PLATE"));
+        creation.setSpaceId(new SpacePermId(spaceCode));
+        creation.setCreationId(new CreationId("creation " + code));
+        return creation;
     }
 
     private SamplePermId createCisdSample(String code)
diff --git a/openbis/sourceTest/sql/postgresql/149/014=data_set_types.tsv b/openbis/sourceTest/sql/postgresql/149/014=data_set_types.tsv
index 31b8fa819a2..310b618a6a3 100644
--- a/openbis/sourceTest/sql/postgresql/149/014=data_set_types.tsv
+++ b/openbis/sourceTest/sql/postgresql/149/014=data_set_types.tsv
@@ -6,3 +6,4 @@
 6	VALIDATED_CONTAINER_TYPE	A container test-validated data set type	2011-05-09 12:24:44.462776+02	\N	\N	f	CONTAINER	11
 7	VALIDATED_IMPOSSIBLE_TO_UPDATE_TYPE	A dataset type impossible to updatea set type	2011-05-09 12:24:44.462776+02	\N	\N	f	PHYSICAL	6
 8	VALIDATED_NORMAL_TYPE	A regular test-validated type	2011-05-09 12:24:00+02	\N	\N	f	PHYSICAL	11
+9	REQUIRES_EXPERIMENT	Requires experiment	2009-03-23 15:34:44.462776+01	\N	\N	f	PHYSICAL	\N
-- 
GitLab