From 9def66150b21799e96cf394ab240fd550db25435 Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Thu, 5 Mar 2015 10:59:35 +0000
Subject: [PATCH] SSDM-1366: datastore_server: merge feature branch
 'data-set-sample' with trunk.

SVN: 33561
---
 .../DataSetRegistrationAlgorithm.java         |  27 +--
 .../cisd/etlserver/DataStrategyStore.java     | 168 +++++++++++-------
 .../SampleAndDataSetRegistrator.java          |   6 +-
 .../TemplateBasedLinkNamingStrategy.java      |  16 +-
 .../etlserver/plugins/grouping/Grouping.java  |   8 +-
 .../Hdf5CompressingPostRegistrationTask.java  |   4 +-
 .../api/v1/impl/DataSetImmutable.java         |  63 +++++--
 .../api/v2/impl/DataSetImmutable.java         |  61 ++++++-
 .../dss/generic/server/MetaDataBuilder.java   |  19 +-
 .../dss/generic/server/UploadingCommand.java  |  12 +-
 .../AbstractDropboxProcessingPlugin.java      |  30 +++-
 .../AbstractDataSetFileOperationsManager.java |   5 +-
 .../shared/ExperimentBasedShareFinder.java    |  17 +-
 .../IdentifierAttributeMappingManager.java    |   8 +
 .../TransferredDataSetHandlerTest.java        |  13 +-
 15 files changed, 318 insertions(+), 139 deletions(-)

diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java
index 9e91c50cc81..cbfb5cff099 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java
@@ -51,6 +51,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.StorageFormat;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 
 /**
@@ -123,8 +124,6 @@ public class DataSetRegistrationAlgorithm
 
         private final String defaultErrorMessageTemplate;
 
-        private final String emailSubjectTemplate;
-
         public DataSetRegistrationAlgorithmState(File incomingDataSetFile,
                 IEncapsulatedOpenBISService openBisService,
                 IDelegatedActionWithResult<Boolean> cleanAfterwardsAction,
@@ -164,7 +163,6 @@ public class DataSetRegistrationAlgorithm
             dataSetInformation.setDataSetType(dataSetType);
             this.storeRoot = storageProcessor.getStoreRootDirectory();
             this.defaultErrorMessageTemplate = DATA_SET_STORAGE_FAILURE_TEMPLATE;
-            this.emailSubjectTemplate = EMAIL_SUBJECT_TEMPLATE;
         }
 
         public File getIncomingDataSetFile()
@@ -179,7 +177,7 @@ public class DataSetRegistrationAlgorithm
     static private final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
             DataSetRegistrationAlgorithm.class);
 
-    public static final String EMAIL_SUBJECT_TEMPLATE = "Success: data set for experiment '%s";
+    public static final String EMAIL_SUBJECT_TEMPLATE = "Success: data set for sample or experiment '%s";
 
     public static final String DATA_SET_REGISTRATION_FAILURE_TEMPLATE =
             "Registration of data set '%s' failed.";
@@ -264,12 +262,9 @@ public class DataSetRegistrationAlgorithm
             boolean deleted = false;
             if (userEmailOrNull != null)
             {
-                final String errorMessage =
-                        "Error when trying to register data set '" + incomingDataSetFile.getName()
-                                + "'.";
-                state.mailClient.sendMessage(String.format(errorMessage, dataSetInformation
-                        .getExperimentIdentifier().getExperimentCode()), ex.getMessage(), null,
-                        null, userEmailOrNull);
+                final String errorMessage = "Error when trying to register data set '"
+                        + incomingDataSetFile.getName() + "'.";
+                state.mailClient.sendMessage(errorMessage, ex.getMessage(), null, null, userEmailOrNull);
                 if (state.shouldDeleteUnidentified)
                 {
                     deleted = removeAndLog(errorMessage + " [" + ex.getMessage() + "]");
@@ -498,8 +493,16 @@ public class DataSetRegistrationAlgorithm
             }
             if (StringUtils.isBlank(email) == false)
             {
-                state.mailClient.sendMessage(String.format(state.emailSubjectTemplate,
-                        dataSetInformation.getExperimentIdentifier().getExperimentCode()), msg,
+                String code = dataSetInformation.getSampleCode();
+                if (code == null)
+                {
+                    ExperimentIdentifier experimentIdentifier = dataSetInformation.getExperimentIdentifier();
+                    if (experimentIdentifier != null)
+                    {
+                        code = experimentIdentifier.getExperimentCode();
+                    }
+                }
+                state.mailClient.sendMessage(String.format(EMAIL_SUBJECT_TEMPLATE, code), msg,
                         null, null, email);
             }
         }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataStrategyStore.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataStrategyStore.java
index 55dacbe72fe..00d503a1dcf 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataStrategyStore.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataStrategyStore.java
@@ -36,6 +36,7 @@ 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.Person;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 
@@ -50,6 +51,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 public class DataStrategyStore implements IDataStrategyStore
 {
     static final String SUBJECT_FORMAT = "ATTENTION: experiment '%s'";
+    static final String SUBJECT_SAMPLE_FORMAT = "ATTENTION: sample '%s'";
 
     private static final Logger notificationLog = LogFactory.getLogger(LogCategory.NOTIFY,
             DataStrategyStore.class);
@@ -137,100 +139,65 @@ public class DataStrategyStore implements IDataStrategyStore
             return dataStoreStrategies.get(DataStoreStrategyKey.UNIDENTIFIED);
         }
 
-        if (dataSetInfo.isNoFileDataSet())
-        {
-            assert incomingDataSetPath == null : "Incoming data set path for a no-file data set must be null";
-        } else
-        {
-            assert incomingDataSetPath != null : "Incoming data set path for a normal data set can not be null.";
-        }
-
-        String containerDatasetPermId = dataSetInfo.tryGetContainerDatasetPermId();
-        if (containerDatasetPermId != null)
-        {
-            AbstractExternalData container =
-                    openbisServiceWrapper.tryGetDataSet(containerDatasetPermId);
-            if (container != null)
-            {
-                dataSetInfo.setContainerDataSet(container);
-            }
-        }
+        assertIncomingDataSetPath(incomingDataSetPath, dataSetInfo);
+        injectContainerDataSet(dataSetInfo);
 
         String emailOrNull = dataSetInfo.tryGetUploadingUserEmail();
-        ExperimentIdentifier experimentIdentifier;
+        ExperimentIdentifier experimentIdentifier = dataSetInfo.getExperimentIdentifier();
         final SampleIdentifier sampleIdentifier = dataSetInfo.getSampleIdentifier();
         if (sampleIdentifier == null)
         {
-            experimentIdentifier = dataSetInfo.getExperimentIdentifier();
-            Experiment experiment = dataSetInfo.tryToGetExperiment();
-            if (experiment == null)
-            {
-                experiment = openbisServiceWrapper.tryGetExperiment(experimentIdentifier);
-            }
+            Experiment experiment = tryGetExperiment(dataSetInfo);
             if (experiment == null)
             {
                 error(emailOrNull, "Unknown experiment identifier '" + experimentIdentifier + "'.");
                 return dataStoreStrategies.get(DataStoreStrategyKey.UNIDENTIFIED);
             }
-            if (experiment.getDeletion() != null)
-            {
-                error(emailOrNull, "Experiment '" + experimentIdentifier + "' has been deleted.");
-                return dataStoreStrategies.get(DataStoreStrategyKey.UNIDENTIFIED);
-            }
             dataSetInfo.setExperiment(experiment);
         } else
         {
-            Sample sample = dataSetInfo.tryToGetSample();
-            if (sample == null)
-            {
-                sample = openbisServiceWrapper.tryGetSample(sampleIdentifier);
-            }
-
+            Sample sample = tryGetSample(dataSetInfo);
             if (sample == null)
             {
                 error(emailOrNull, createNotificationMessage(dataSetInfo, incomingDataSetPath));
                 return dataStoreStrategies.get(DataStoreStrategyKey.UNIDENTIFIED);
             }
+            dataSetInfo.setSample(sample);
             final Experiment experiment = sample.getExperiment();
             if (experiment == null)
             {
-                error(emailOrNull, String.format("Data set for sample '%s' can not be registered "
-                        + "because the sample is not attached to an experiment.", sampleIdentifier));
-                return dataStoreStrategies.get(DataStoreStrategyKey.UNIDENTIFIED);
-            } else if (experiment.getDeletion() != null)
+                if (sample.getSpace() == null) 
+                {
+                    error(emailOrNull, String.format("Data set for sample '%s' can not be registered "
+                            + "because the sample is a shared sample not assigned to a particular space.", 
+                            sampleIdentifier));
+                    return dataStoreStrategies.get(DataStoreStrategyKey.UNIDENTIFIED);
+                }
+            } else 
             {
-                error(emailOrNull, String.format("Data set for sample '%s' can not be registered "
-                        + "because experiment '%s' has been deleted.", sampleIdentifier,
-                        experiment.getCode()));
-                return dataStoreStrategies.get(DataStoreStrategyKey.UNIDENTIFIED);
+                experimentIdentifier = new ExperimentIdentifier(experiment);
+                dataSetInfo.setExperimentIdentifier(experimentIdentifier);
             }
-            dataSetInfo.setSample(sample);
-            experimentIdentifier = new ExperimentIdentifier(experiment);
-            dataSetInfo.setExperimentIdentifier(experimentIdentifier);
 
             final IEntityProperty[] properties =
                     openbisServiceWrapper.tryGetPropertiesOfSample(sampleIdentifier);
             if (properties == null)
             {
-                final Person registrator = experiment.getRegistrator();
+                final Person registrator = experiment == null ? sample.getRegistrator() : experiment.getRegistrator();
                 assert registrator != null : "Registrator must be known";
                 final String message = createInvalidSampleCodeMessage(dataSetInfo);
                 final String recipientMail = registrator.getEmail();
                 if (StringUtils.isNotBlank(recipientMail))
                 {
-                    sendEmail(message, experimentIdentifier, recipientMail);
+                    sendEmail(message, experimentIdentifier, sampleIdentifier, recipientMail);
                 } else
                 {
                     error(emailOrNull, "The registrator '" + registrator
                             + "' has a blank email, sending the following email failed:\n"
                             + message);
                 }
-                operationLog.error(String.format("Incoming data set '%s' claims to "
-                        + "belong to experiment '%s' and sample"
-                        + " identifier '%s', but according to the openBIS server "
-                        + "there is no such sample for this "
-                        + "experiment (maybe it has been deleted?). We thus consider it invalid.",
-                        incomingDataSetPath, experimentIdentifier, sampleIdentifier));
+                operationLog.error(createLogMessageForMissingSampleProperties(incomingDataSetPath, 
+                        experimentIdentifier, sampleIdentifier));
                 return dataStoreStrategies.get(DataStoreStrategyKey.INVALID);
             }
             dataSetInfo.setSampleProperties(properties);
@@ -238,13 +205,87 @@ public class DataStrategyStore implements IDataStrategyStore
 
         if (operationLog.isInfoEnabled())
         {
-            operationLog.info("Identified that database knows experiment '" + experimentIdentifier
-                    + "'"
-                    + (sampleIdentifier == null ? "." : " and sample '" + sampleIdentifier + "'."));
+            operationLog.info(createLogMessageForIdentified(experimentIdentifier, sampleIdentifier));
         }
         return dataStoreStrategies.get(DataStoreStrategyKey.IDENTIFIED);
     }
 
+    private String createLogMessageForIdentified(ExperimentIdentifier experimentIdentifier,
+            SampleIdentifier sampleIdentifier)
+    {
+        String prefix = "Identified that database knows ";
+        if (sampleIdentifier == null)
+        {
+            return prefix + "experiment '" + experimentIdentifier + "'.";
+        } else if (experimentIdentifier == null)
+        {
+            return prefix + "sample '" + sampleIdentifier + "'.";
+        }
+        return prefix + "experiment '" + experimentIdentifier + "' and sample '" + sampleIdentifier + "'.";
+    }
+
+    private String createLogMessageForMissingSampleProperties(final File incomingDataSetPath,
+            ExperimentIdentifier experimentIdentifier, final SampleIdentifier sampleIdentifier)
+    {
+        String claimedOwner;
+        if (experimentIdentifier == null)
+        {
+            claimedOwner = String.format("sample '%s'",  sampleIdentifier);
+        } else
+        {
+            claimedOwner = String.format("experiment '%s' and sample '%s'", 
+                    experimentIdentifier, sampleIdentifier);
+        }
+        return String.format("Incoming data set '%s' claims to belong to %s, but according to the openBIS server "
+                + "there is no such sample (maybe it has been deleted?). We thus consider it invalid.",
+                incomingDataSetPath, claimedOwner);
+    }
+
+    public Sample tryGetSample(final DataSetInformation dataSetInfo)
+    {
+        Sample sample = dataSetInfo.tryToGetSample();
+        if (sample == null)
+        {
+            sample = openbisServiceWrapper.tryGetSample(dataSetInfo.getSampleIdentifier());
+        }
+        return sample;
+    }
+
+    public Experiment tryGetExperiment(final DataSetInformation dataSetInfo)
+    {
+        Experiment experiment = dataSetInfo.tryToGetExperiment();
+        if (experiment == null)
+        {
+            experiment = openbisServiceWrapper.tryGetExperiment(dataSetInfo.getExperimentIdentifier());
+        }
+        return experiment;
+    }
+
+    public void injectContainerDataSet(final DataSetInformation dataSetInfo)
+    {
+        String containerDatasetPermId = dataSetInfo.tryGetContainerDatasetPermId();
+        if (containerDatasetPermId != null)
+        {
+            AbstractExternalData container =
+                    openbisServiceWrapper.tryGetDataSet(containerDatasetPermId);
+            if (container != null)
+            {
+                dataSetInfo.setContainerDataSet(container);
+            }
+        }
+    }
+
+    public void assertIncomingDataSetPath(final File incomingDataSetPath, final DataSetInformation dataSetInfo)
+    {
+        if (dataSetInfo.isNoFileDataSet())
+        {
+            assert incomingDataSetPath == null : "Incoming data set path for a no-file data set must be null";
+        } else
+        {
+            assert incomingDataSetPath != null : "Incoming data set path for a normal data set can not be null.";
+        }
+    }
+
     private void error(String emailOrNull, String message)
     {
         if (emailOrNull == null)
@@ -259,9 +300,16 @@ public class DataStrategyStore implements IDataStrategyStore
     }
 
     private void sendEmail(final String message, final ExperimentIdentifier experimentIdentifier,
-            final String recipientMail)
+            SampleIdentifier sampleIdentifier, final String recipientMail)
     {
-        final String subject = String.format(SUBJECT_FORMAT, experimentIdentifier);
+        final String subject;
+        if (experimentIdentifier == null)
+        {
+            subject = String.format(SUBJECT_SAMPLE_FORMAT, sampleIdentifier);
+        } else
+        {
+            subject = String.format(SUBJECT_FORMAT, experimentIdentifier);
+        }
         try
         {
             mailClient.sendMessage(subject, message, null, null, recipientMail);
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/entityregistration/SampleAndDataSetRegistrator.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/entityregistration/SampleAndDataSetRegistrator.java
index 097138a8e77..e82a3f93fa3 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/entityregistration/SampleAndDataSetRegistrator.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/entityregistration/SampleAndDataSetRegistrator.java
@@ -79,7 +79,7 @@ class SampleAndDataSetRegistrator extends AbstractSampleAndDataSetProcessor impl
         try
         {
             checkDataSetFileNotEmpty();
-            checkExperimentExists();
+            injectExperiment();
             retrieveSampleOrNull();
         } catch (UserFailureException ex)
         {
@@ -188,13 +188,13 @@ class SampleAndDataSetRegistrator extends AbstractSampleAndDataSetProcessor impl
         sampleOrNull = globalState.getOpenbisService().tryGetSampleWithExperiment(sampleIdentifier);
     }
 
-    private void checkExperimentExists()
+    private void injectExperiment()
     {
         ExperimentIdentifier experimentId =
                 sampleDataSetPair.getDataSetInformation().getExperimentIdentifier();
         if (null == experimentId)
         {
-            throw new UserFailureException("An experiment identifier must be specified");
+            return;
         }
 
         Experiment experiment = globalState.getOpenbisService().tryGetExperiment(experimentId);
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/TemplateBasedLinkNamingStrategy.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/TemplateBasedLinkNamingStrategy.java
index e88e246064d..9660f7d14e0 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/TemplateBasedLinkNamingStrategy.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/TemplateBasedLinkNamingStrategy.java
@@ -154,12 +154,7 @@ public class TemplateBasedLinkNamingStrategy implements IHierarchicalStorageLink
             @Override
             String extractValueFromData(SimpleDataSetInformationDTO data)
             {
-                String samplePathElement = data.getSampleCode();
-                if (samplePathElement == null)
-                {
-                    samplePathElement = NOT_DIRECTLY_CONNECTED;
-                }
-                return samplePathElement;
+                return getPathElement(data.getSampleCode());
             }
 
         },
@@ -170,7 +165,7 @@ public class TemplateBasedLinkNamingStrategy implements IHierarchicalStorageLink
             @Override
             String extractValueFromData(SimpleDataSetInformationDTO data)
             {
-                return data.getExperimentCode();
+                return getPathElement(data.getExperimentCode());
             }
 
         },
@@ -180,7 +175,7 @@ public class TemplateBasedLinkNamingStrategy implements IHierarchicalStorageLink
             @Override
             String extractValueFromData(SimpleDataSetInformationDTO data)
             {
-                return data.getProjectCode();
+                return getPathElement(data.getProjectCode());
             }
         },
 
@@ -208,5 +203,10 @@ public class TemplateBasedLinkNamingStrategy implements IHierarchicalStorageLink
         
         abstract String extractValueFromData(SimpleDataSetInformationDTO data);
 
+        private static String getPathElement(String pathElement)
+        {
+            return pathElement == null ? NOT_DIRECTLY_CONNECTED : pathElement;
+        }
+
     }
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/grouping/Grouping.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/grouping/Grouping.java
index fd15217a20a..3be39aa57b5 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/grouping/Grouping.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/plugins/grouping/Grouping.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.etlserver.plugins.grouping;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 
 /**
@@ -37,7 +38,7 @@ public enum Grouping implements IGroupKeyProvider
         @Override
         public String getGroupKey(AbstractExternalData dataset)
         {
-            return dataset.getExperiment().getProject().getSpace().getCode();
+            return dataset.getSpace().getCode();
         }
     },
     Project
@@ -53,7 +54,8 @@ public enum Grouping implements IGroupKeyProvider
         @Override
         public String getGroupKey(AbstractExternalData dataset)
         {
-            return dataset.getExperiment().getIdentifier();
+            Experiment experiment = dataset.getExperiment();
+            return experiment != null ? experiment.getIdentifier() : "no_experiment";
         }
 
     },
@@ -87,7 +89,7 @@ public enum Grouping implements IGroupKeyProvider
         @Override
         public String getGroupKey(AbstractExternalData dataset)
         {
-            return dataset.getExperiment().getIdentifier() + "#" + dataset.getDataSetType().getCode();
+            return Experiment.getGroupKey(dataset) + "#" + DataSetType.getGroupKey(dataset);
         }
     }
 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/Hdf5CompressingPostRegistrationTask.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/Hdf5CompressingPostRegistrationTask.java
index 4308ea02a0b..629be03cead 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/Hdf5CompressingPostRegistrationTask.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/Hdf5CompressingPostRegistrationTask.java
@@ -46,6 +46,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Code;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ContainerDataSet;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PhysicalDataSet;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
@@ -433,7 +434,8 @@ public class Hdf5CompressingPostRegistrationTask extends AbstractPostRegistratio
 
     private ExperimentIdentifier extractExperimentIdentifier(AbstractExternalData data)
     {
-        return ExperimentIdentifierFactory.parse(data.getExperiment().getIdentifier());
+        Experiment experiment = data.getExperiment();
+        return experiment == null ? null : ExperimentIdentifierFactory.parse(experiment.getIdentifier());
     }
 
     private SampleIdentifier extractSampleIdentifier(AbstractExternalData data)
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetImmutable.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetImmutable.java
index 612d4945cac..921f79397b3 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetImmutable.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetImmutable.java
@@ -29,11 +29,13 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.ISampleImmuta
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.IObjectId;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.dataset.DataSetCodeId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Code;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ContainerDataSet;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalDataManagementSystem;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
 
 /**
@@ -43,6 +45,52 @@ import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
  */
 public class DataSetImmutable extends AbstractDataSetImmutable
 {
+    private static final class NullExperiment implements IExperimentImmutable
+    {
+        private String message;
+
+        NullExperiment(String dataSetCode)
+        {
+            message = "No experiment defined for data set '" + dataSetCode + "'.";
+        }
+
+        @Override
+        public IObjectId getEntityId()
+        {
+            throw new UnsupportedOperationException(message);
+        }
+
+        @Override
+        public String getExperimentIdentifier()
+        {
+            throw new UnsupportedOperationException(message);
+        }
+
+        @Override
+        public boolean isExistingExperiment()
+        {
+            return false;
+        }
+
+        @Override
+        public String getExperimentType()
+        {
+            return null;
+        }
+
+        @Override
+        public String getPropertyValue(String propertyCode)
+        {
+            return null;
+        }
+
+        @Override
+        public String getPermId()
+        {
+            throw new UnsupportedOperationException(message);
+        }
+    }
+    
     protected final AbstractExternalData dataSet;
 
     public DataSetImmutable(AbstractExternalData dataSet, IEncapsulatedBasicOpenBISService service)
@@ -66,20 +114,15 @@ public class DataSetImmutable extends AbstractDataSetImmutable
     @Override
     public IExperimentImmutable getExperiment()
     {
-        return new ExperimentImmutable(dataSet.getExperiment());
+        Experiment experiment = dataSet.getExperiment();
+        return experiment == null ? new NullExperiment(getDataSetCode()) : new ExperimentImmutable(experiment);
     }
 
     @Override
     public ISampleImmutable getSample()
     {
-        ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample sample = dataSet.getSample();
-        if (sample == null)
-        {
-            return null;
-        } else
-        {
-            return new SampleImmutable(sample);
-        }
+        Sample sample = dataSet.getSample();
+        return sample == null ? null : new SampleImmutable(sample);
     }
 
     @Override
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/impl/DataSetImmutable.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/impl/DataSetImmutable.java
index 2da13d1f84f..40e83d1c231 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/impl/DataSetImmutable.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v2/impl/DataSetImmutable.java
@@ -32,8 +32,10 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.dataset.DataSetCode
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Code;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ContainerDataSet;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalDataManagementSystem;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
 
 /**
@@ -43,6 +45,52 @@ import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
  */
 public class DataSetImmutable extends AbstractDataSetImmutable
 {
+    private static final class NullExperiment implements IExperimentImmutable
+    {
+        private String message;
+
+        NullExperiment(String dataSetCode)
+        {
+            message = "No experiment defined for data set '" + dataSetCode + "'.";
+        }
+
+        @Override
+        public IObjectId getEntityId()
+        {
+            throw new UnsupportedOperationException(message);
+        }
+
+        @Override
+        public String getExperimentIdentifier()
+        {
+            throw new UnsupportedOperationException(message);
+        }
+
+        @Override
+        public boolean isExistingExperiment()
+        {
+            return false;
+        }
+
+        @Override
+        public String getExperimentType()
+        {
+            return null;
+        }
+
+        @Override
+        public String getPropertyValue(String propertyCode)
+        {
+            return null;
+        }
+
+        @Override
+        public String getPermId()
+        {
+            throw new UnsupportedOperationException(message);
+        }
+    }
+    
     protected final AbstractExternalData dataSet;
 
     public DataSetImmutable(AbstractExternalData dataSet, IEncapsulatedBasicOpenBISService service)
@@ -66,20 +114,15 @@ public class DataSetImmutable extends AbstractDataSetImmutable
     @Override
     public IExperimentImmutable getExperiment()
     {
-        return new ExperimentImmutable(dataSet.getExperiment());
+        Experiment experiment = dataSet.getExperiment();
+        return experiment == null ? new NullExperiment(getDataSetCode()) : new ExperimentImmutable(experiment);
     }
 
     @Override
     public ISampleImmutable getSample()
     {
-        ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample sample = dataSet.getSample();
-        if (sample == null)
-        {
-            return null;
-        } else
-        {
-            return new SampleImmutable(sample);
-        }
+        Sample sample = dataSet.getSample();
+        return sample == null ? null : new SampleImmutable(sample);
     }
 
     @Override
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java
index fc8934a5f12..ee9363997cf 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java
@@ -130,14 +130,17 @@ public class MetaDataBuilder
             builder.sampleProperties(sample.getProperties());
         }
         Experiment experiment = dataSet.getExperiment();
-        Project project = experiment.getProject();
-        builder.experiment("space_code", project.getSpace().getCode());
-        builder.experiment("project_code", project.getCode());
-        builder.experiment("experiment_code", experiment.getCode());
-        builder.experiment("experiment_type_code", experiment.getExperimentType().getCode());
-        builder.experiment("registration_timestamp", experiment.getRegistrationDate());
-        builder.experiment("registrator", experiment.getRegistrator());
-        builder.experimentProperties(experiment.getProperties());
+        if (experiment != null)
+        {
+            Project project = experiment.getProject();
+            builder.experiment("space_code", project.getSpace().getCode());
+            builder.experiment("project_code", project.getCode());
+            builder.experiment("experiment_code", experiment.getCode());
+            builder.experiment("experiment_type_code", experiment.getExperimentType().getCode());
+            builder.experiment("registration_timestamp", experiment.getRegistrationDate());
+            builder.experiment("registrator", experiment.getRegistrator());
+            builder.experimentProperties(experiment.getProperties());
+        }
         return builder.getRenderedMetaData();
     }
 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java
index 0adce36e2a7..ea9c31dc70f 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java
@@ -326,9 +326,15 @@ class UploadingCommand implements IDataSetCommand
     {
         Sample sample = dataSet.getSample();
         Experiment experiment = dataSet.getExperiment();
-        Project project = experiment.getProject();
-        return project.getSpace().getCode() + "/" + project.getCode() + "/" + experiment.getCode()
-                + "/" + (sample == null ? "" : sample.getCode() + "/") + dataSet.getCode();
+        if (experiment != null)
+        {
+            Project project = experiment.getProject();
+            return project.getSpace().getCode() + "/" + project.getCode() + "/" + experiment.getCode()
+                    + "/" + (sample == null ? "" : sample.getCode() + "/") + dataSet.getCode();
+        } else
+        {
+            return sample.getSpace().getCode() + "/" + sample.getCode() + "/" + dataSet.getCode();
+        }
     }
 
     private void sendEMail(String message)
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java
index 3bfae5ee1c3..057e02fb439 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java
@@ -40,6 +40,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.BasicConstant;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 
 /**
  * The base class for processing plugins that employ a {@link IPostRegistrationDatasetHandler}.
@@ -116,9 +118,10 @@ abstract public class AbstractDropboxProcessingPlugin extends AbstractDatastoreP
                 render(dataSetDescription.getSampleIdentifier(),
                         dataSetDescription.getSampleTypeCode());
         boolean withSample = sampleOrNull != null;
+        boolean withExperiment = experiment != null;
         boolean processingFailed = status.isError();
         String processingDescription = getProcessingDescription(dataSetDescription, context);
-        Template template = getEMailMessageTemplate(processingFailed, withSample).createFreshCopy();
+        Template template = getEMailMessageTemplate(processingFailed, withSample, withExperiment).createFreshCopy();
         String subject;
         if (processingFailed)
         {
@@ -130,7 +133,10 @@ abstract public class AbstractDropboxProcessingPlugin extends AbstractDatastoreP
         }
         template.bind("processing-description", processingDescription);
         template.bind("data-set", dataSet);
-        template.bind("experiment", experiment);
+        if (withExperiment)
+        {
+            template.bind("experiment", experiment);
+        }
         if (withSample)
         {
             template.bind("sample", sampleOrNull);
@@ -144,14 +150,14 @@ abstract public class AbstractDropboxProcessingPlugin extends AbstractDatastoreP
         mailClient.sendEmailMessage(subject, template.createText(), null, null, eMailAddress);
     }
 
-    private Template getEMailMessageTemplate(boolean withError, boolean withSample)
+    private Template getEMailMessageTemplate(boolean withError, boolean withSample, boolean withExperiment)
     {
         return new Template(
                 (withError ? "Processing of data set ${data-set} failed.\nReason: ${error}"
                         : "Successfully processed data set ${data-set}.")
                         + "\n\nProcessing details:\n"
                         + "Description: ${processing-description}\n"
-                        + "Experiment: ${experiment}\n"
+                        + (withExperiment ? "Experiment: ${experiment}\n" : "")
                         + (withSample ? "Sample: ${sample}\n" : "")
                         + "Started: ${start-time}.\n" + "Finished: ${end-time}.");
     }
@@ -201,10 +207,18 @@ abstract public class AbstractDropboxProcessingPlugin extends AbstractDatastoreP
         datasetInfo.setSampleCode(dataset.getSampleCode());
         datasetInfo.setSpaceCode(dataset.getSpaceCode());
         datasetInfo.setDataSetCode(dataset.getDataSetCode());
-        ExperimentIdentifier expIdent =
-                new ExperimentIdentifier(null, dataset.getSpaceCode(), dataset.getProjectCode(),
-                        dataset.getExperimentCode());
-        datasetInfo.setExperimentIdentifier(expIdent);
+        if (dataset.getExperimentCode() != null)
+        {
+            ExperimentIdentifier expIdent =
+                    new ExperimentIdentifier(null, dataset.getSpaceCode(), dataset.getProjectCode(),
+                            dataset.getExperimentCode());
+            datasetInfo.setExperimentIdentifier(expIdent);
+        }
+        if (dataset.getSampleCode() != null)
+        {
+            SpaceIdentifier spaceIdentifier = new SpaceIdentifier(dataset.getSpaceCode());
+            datasetInfo.setSampleIdentifier(new SampleIdentifier(spaceIdentifier, dataset.getSampleCode()));
+        }
         return datasetInfo;
     }
 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/archiver/AbstractDataSetFileOperationsManager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/archiver/AbstractDataSetFileOperationsManager.java
index 5d6187ea24c..1f4d65d59ba 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/archiver/AbstractDataSetFileOperationsManager.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/archiver/AbstractDataSetFileOperationsManager.java
@@ -91,7 +91,10 @@ public abstract class AbstractDataSetFileOperationsManager
     {
         AbstractExternalData dataSet = getService().tryGetDataSet(datasetDescription.getDataSetCode());
         String experimentIdentifier = datasetDescription.getExperimentIdentifier();
-        dataSet.setExperiment(getService().tryGetExperiment(ExperimentIdentifierFactory.parse(experimentIdentifier)));
+        if (experimentIdentifier != null)
+        {
+            dataSet.setExperiment(getService().tryGetExperiment(ExperimentIdentifierFactory.parse(experimentIdentifier)));
+        }
         String sampleIdentifier = datasetDescription.getSampleIdentifier();
         if (sampleIdentifier != null)
         {
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ExperimentBasedShareFinder.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ExperimentBasedShareFinder.java
index da9b9daf483..64f5c0875b3 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ExperimentBasedShareFinder.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ExperimentBasedShareFinder.java
@@ -42,15 +42,18 @@ public class ExperimentBasedShareFinder implements IShareFinder
     @Override
     public Share tryToFindShare(SimpleDataSetInformationDTO dataSet, List<Share> shares)
     {
-        String experimentIdentifier =
-                new ExperimentIdentifier(null, dataSet.getSpaceCode(), dataSet.getProjectCode(),
-                        dataSet.getExperimentCode()).toString();
-        for (Share share : shares)
+        if (dataSet.getExperimentCode() != null)
         {
-            if (share.getExperimentIdentifiers().contains(experimentIdentifier)
-                    && share.calculateFreeSpace() > dataSet.getDataSetSize())
+            String experimentIdentifier =
+                    new ExperimentIdentifier(null, dataSet.getSpaceCode(), dataSet.getProjectCode(),
+                            dataSet.getExperimentCode()).toString();
+            for (Share share : shares)
             {
-                return share;
+                if (share.getExperimentIdentifiers().contains(experimentIdentifier)
+                        && share.calculateFreeSpace() > dataSet.getDataSetSize())
+                {
+                    return share;
+                }
             }
         }
         return null;
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IdentifierAttributeMappingManager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IdentifierAttributeMappingManager.java
index d0feefffc9a..e12b21f2652 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IdentifierAttributeMappingManager.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IdentifierAttributeMappingManager.java
@@ -270,12 +270,20 @@ public class IdentifierAttributeMappingManager
 
     private Attributes tryGetExperimentAttributes(String spaceCode, String projectCode, String experimentCode)
     {
+        if (experimentCode == null)
+        {
+            return null;
+        }
         String identifier = new ExperimentIdentifier(null, spaceCode, projectCode, experimentCode).toString();
         return getAttributesMap().get(identifier);
     }
 
     private Attributes tryGetProjectAttributes(String spaceCode, String projectCode)
     {
+        if (projectCode == null)
+        {
+            return null;
+        }
         String identifier = new ProjectIdentifier(null, spaceCode, projectCode).toString();
         return getAttributesMap().get(identifier);
     }
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
index 044e6ced80c..3e4b4c97aab 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
@@ -77,6 +77,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.OpenBISSessionHolder;
 import ch.systemsx.cisd.openbis.generic.shared.dto.StorageFormat;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 import ch.systemsx.cisd.openbis.util.LogRecordingUtils;
 
 /**
@@ -486,9 +487,11 @@ public final class TransferredDataSetHandlerTest extends AbstractFileSystemTestC
     private final void checkSuccessEmailNotification(final Expectations expectations,
             final DataSetInformation dataSet, final String dataSetCode, final String recipient)
     {
+        SampleIdentifier sampleIdentifier = dataSet.getSampleIdentifier();
+        String code = sampleIdentifier != null ? sampleIdentifier.getSampleCode() :
+                dataSet.getExperimentIdentifier().getExperimentCode();
         expectations.one(mailClient).sendMessage(
-                String.format(DataSetRegistrationHelper.EMAIL_SUBJECT_TEMPLATE, dataSet
-                        .getExperimentIdentifier().getExperimentCode()),
+                String.format(DataSetRegistrationHelper.EMAIL_SUBJECT_TEMPLATE, code),
                 getNotificationEmailContent(dataSet, dataSetCode), null, null, recipient);
     }
 
@@ -662,10 +665,8 @@ public final class TransferredDataSetHandlerTest extends AbstractFileSystemTestC
         assertEquals(false, isFinishedData1.exists());
         assertLog("ERROR OPERATION.DataStrategyStore - "
                 + "Incoming data set '<wd>/data1' claims to belong to experiment "
-                + "'/GROUP1/PROJECT1/EXPERIMENT1' and sample identifier '/"
-                + SAMPLE_CODE
-                + "', "
-                + "but according to the openBIS server there is no such sample for this experiment "
+                + "'/GROUP1/PROJECT1/EXPERIMENT1' and sample '/" + SAMPLE_CODE + "', "
+                + "but according to the openBIS server there is no such sample "
                 + "(maybe it has been deleted?). We thus consider it invalid."
                 + OSUtilities.LINE_SEPARATOR + "INFO  OPERATION.FileRenamer - "
                 + "Moving file 'data1' from '<wd>' to '<wd>/1/invalid/DataSetType_O1'.");
-- 
GitLab