From f8d9d205c37742727a1fc612b491ac6bd6f3a207 Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Wed, 9 Sep 2009 13:44:58 +0000
Subject: [PATCH] LMS-1165 Extract parent data set codes in
 DefaultDataSetInfoExtractor

SVN: 12535
---
 datastore_server/dist/etc/service.properties  |  4 +-
 .../AbstractDataSetInfoExtractor.java         |  5 +
 .../cisd/etlserver/BDSStorageProcessor.java   | 15 +--
 .../DefaultDataSetInfoExtractor.java          | 99 ++++++++++++-------
 .../etlserver/TransferredDataSetHandler.java  |  9 +-
 .../DataSetInfoExtractorForImageAnalysis.java |  5 +-
 .../shared/dto/DataSetInformation.java        |  9 +-
 .../etlserver/CodeExtractortTestCase.java     |  5 +-
 .../DefaultDataSetInfoExtractorTest.java      | 15 ++-
 .../TransferredDataSetHandlerTest.java        | 12 ++-
 ...etInfoExtractorForDataAcquisitionTest.java |  5 +-
 ...aSetInfoExtractorForImageAnalysisTest.java |  3 +-
 12 files changed, 114 insertions(+), 72 deletions(-)

diff --git a/datastore_server/dist/etc/service.properties b/datastore_server/dist/etc/service.properties
index a0ad5b4da2a..2ebaa4dfb48 100644
--- a/datastore_server/dist/etc/service.properties
+++ b/datastore_server/dist/etc/service.properties
@@ -137,8 +137,8 @@ main-thread.data-set-info-extractor = ch.systemsx.cisd.etlserver.DefaultDataSetI
 main-thread.data-set-info-extractor.entity-separator = ${data-set-file-name-entity-separator}
 # The index of the sample code in the name when splitted by the entity-separator
 #main-thread.index-of-sample-code = -1
-# The index of the code of the parent data set (leave that commented out to _not_ have a data set parent)
-#index-of-parent-data-set-code = -2
+# The index of the codes of parent data sets (leave that commented out to _not_ have a data set parent)
+#index-of-parent-data-set-codes = -2
 # The group 
 main-thread.data-set-info-extractor.group-code = TEST
 # Location of file containing data set properties 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractDataSetInfoExtractor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractDataSetInfoExtractor.java
index 0f99bf79097..f2d9357694c 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractDataSetInfoExtractor.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractDataSetInfoExtractor.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Properties;
 
 import ch.rinn.restrictions.Private;
+import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.parser.AbstractParserObjectFactory;
 import ch.systemsx.cisd.common.parser.IParserObjectFactory;
@@ -69,6 +70,10 @@ public abstract class AbstractDataSetInfoExtractor implements IDataSetInfoExtrac
         entitySeparator =
                 PropertyUtils.getChar(properties, ENTITY_SEPARATOR_PROPERTY_NAME,
                         DEFAULT_ENTITY_SEPARATOR);
+        if (Character.isWhitespace(entitySeparator))
+        {
+            throw new ConfigurationFailureException("Entity separator is a whitespace character.");
+        }
     }
 
     protected String getGroupCode()
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/BDSStorageProcessor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/BDSStorageProcessor.java
index 494ce0aef33..6f9072ecb7c 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/BDSStorageProcessor.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/BDSStorageProcessor.java
@@ -18,7 +18,6 @@ package ch.systemsx.cisd.etlserver;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
@@ -295,8 +294,7 @@ public final class BDSStorageProcessor extends AbstractStorageProcessor implemen
             final ITypeExtractor typeExtractor, final File incomingDataSetPath)
     {
         final String dataSetCode = dataSetInformation.getDataSetCode();
-        final String parentDataSetCode = dataSetInformation.getParentDataSetCode();
-        final List<String> parentCodes = getParentCodeList(parentDataSetCode);
+        final List<String> parentCodes = new ArrayList<String>(dataSetInformation.getParentDataSetCodes());
         final boolean isMeasured = typeExtractor.isMeasuredData(incomingDataSetPath);
         final DataSetType dataSetType = typeExtractor.getDataSetType(incomingDataSetPath);
         final DataSet dataSet =
@@ -307,17 +305,6 @@ public final class BDSStorageProcessor extends AbstractStorageProcessor implemen
         return dataSet;
     }
 
-    private final static List<String> getParentCodeList(final String parentDataSetCode)
-    {
-        if (parentDataSetCode == null)
-        {
-            return Collections.<String> emptyList();
-        } else
-        {
-            return Collections.singletonList(parentDataSetCode);
-        }
-    }
-
     private final static String getPathOf(final INode node)
     {
         final StringBuilder builder = new StringBuilder(node.getName());
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractor.java
index ed2244487e3..db0add1c495 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractor.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractor.java
@@ -19,12 +19,17 @@ package ch.systemsx.cisd.etlserver;
 import java.io.File;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
+import java.util.LinkedHashSet;
 import java.util.Properties;
+import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
 
 import ch.rinn.restrictions.Private;
+import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.utilities.PropertyUtils;
@@ -41,10 +46,10 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
  * <li>Data producer code
  * <li>Data production date
  * </ul>
- * The name is split into entities separated by the property {@link #ENTITY_SEPARATOR_PROPERTY_NAME}
- * . It is assumed that each of the above-mentioned pieces of information is one of these entities.
- * The extractor can be configured by the following optional properties:
- * <table border="1" * cellspacing="0" cellpadding="5">
+ * The name is split into entities separated by the property {@link #ENTITY_SEPARATOR_PROPERTY_NAME} .
+ * It is assumed that each of the above-mentioned pieces of information is one of these entities.
+ * The extractor can be configured by the following optional properties: <table border="1" *
+ * cellspacing="0" cellpadding="5">
  * <tr>
  * <th>Property</th>
  * <th>Default value</th>
@@ -53,8 +58,8 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
  * <tr>
  * <td><code>strip-file-extension</code></td>
  * <td><code>false</code></td>
- * <td>If <code>true</code> the file extension will be removed before extracting informations from
- * the file name.</td>
+ * <td>If <code>true</code> the file extension will be removed before extracting informations
+ * from the file name.</td>
  * </tr>
  * <tr>
  * <td><code>entity-separator</code></td>
@@ -62,6 +67,11 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
  * <td>Character which separates entities in the file name. Whitespace characters are not allowed.</td>
  * </tr>
  * <tr>
+ * <td><code>sub-entity-separator</code></td>
+ * <td><code>&amp;</code></td>
+ * <td>Character which separates sub entities of an entity. Whitespace characters are not allowed.</td>
+ * </tr>
+ * <tr>
  * <td><code>index-of-group-code</code></td>
  * <td><code>null</code></td>
  * <td>This should be a group to which the sample connected with the dataset belongs. If not
@@ -80,10 +90,11 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
  * <td>Index of the entity which is interpreted as the sample code.</td>
  * </tr>
  * <tr>
- * <td><code>index-of-parent-data-set-code</code></td>
+ * <td><code>index-of-parent-data-set-codes</code></td>
  * <td>&nbsp;</td>
- * <td>Index of the entity which is interpreted as the parent data set code. If not specified no
- * parent data set code will be extracted.</td>
+ * <td>Index of the entity which is interpreted as parent data set codes. The codes have to be
+ * separated by the sub entity separator. If not specified no parent data set codes will be
+ * extracted.</td>
  * </tr>
  * <tr>
  * <td><code>index-of-data-producer-code</code></td>
@@ -101,22 +112,26 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
  * <td><code>data-production-date-format</code></td>
  * <td><code>yyyyMMddHHmmss</code></td>
  * <td>Format of the data production date. For the correct syntax see <a
- * href="http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html"
- * >SimpleDateFormat</a>.</td>
+ * href="http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html" >SimpleDateFormat</a>.</td>
  * </tr>
  * <tr>
  * <td><code>data-set-properties-file-name</code></td>
  * <td><code>&nbsp;</code></td>
  * <td>Path to a file inside a data set directory which contains data set properties.</td>
  * </tr>
- * </table>
- * The first entity has index 0, the second 1, etc. Using negative numbers one can specify entities
- * from the end. Thus, -1 means the last entity, -2 the second last entity, etc.
+ * </table> The first entity has index 0, the second 1, etc. Using negative numbers one can specify
+ * entities from the end. Thus, -1 means the last entity, -2 the second last entity, etc.
  * 
  * @author Franz-Josef Elmer
  */
 public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
 {
+    /** The name of the property to get sub entity separator from. */
+    @Private
+    static final String SUB_ENTITY_SEPARATOR_PROPERTY_NAME = "sub-entity-separator";
+    
+    /** The default sub entity separator. */
+    protected static final char DEFAULT_SUB_ENTITY_SEPARATOR = '&';
 
     /**
      * Name of the property specifying the index of the entity which should be interpreted as the
@@ -130,15 +145,15 @@ public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
     static final String INDEX_OF_SAMPLE_CODE = "index-of-sample-code";
 
     /**
-     * Name of the property specifying the index of the entity which should be interpreted as the
-     * parent data set code.
+     * Name of the property specifying the index of the entity which should be interpreted as 
+     * parent data set codes.
      * <p>
      * Use a negative number to count from the end, e.g. <code>-1</code> to use the last entity as
      * the sample code.
      * </p>
      */
     @Private
-    static final String INDEX_OF_PARENT_DATA_SET_CODE = "index-of-parent-data-set-code";
+    static final String INDEX_OF_PARENT_DATA_SET_CODES = "index-of-parent-data-set-codes";
 
     /** Default index of sample code. */
     private static final int DEFAULT_INDEX_OF_SAMPLE_CODE = -1;
@@ -195,9 +210,9 @@ public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
 
     private final int indexOfGroupCode;
 
-    private final boolean noParentDataSetCode;
+    private final boolean noParentDataSetCodes;
 
-    private final int indexOfParentDataSetCode;
+    private final int indexOfParentDataSetCodes;
 
     private final boolean noDataProducerCode;
 
@@ -211,9 +226,23 @@ public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
 
     private final String dataSetPropertiesFileNameOrNull;
 
+    private final char subEntitySeparator;
+
     public DefaultDataSetInfoExtractor(final Properties globalProperties)
     {
         super(globalProperties);
+        subEntitySeparator = PropertyUtils.getChar(properties, SUB_ENTITY_SEPARATOR_PROPERTY_NAME,
+                DEFAULT_SUB_ENTITY_SEPARATOR);
+        if (Character.isWhitespace(subEntitySeparator))
+        {
+            throw new ConfigurationFailureException("Sub entity separator is a whitespace character.");
+        }
+        if (subEntitySeparator == entitySeparator)
+        {
+            throw new ConfigurationFailureException("Entity separator '" + entitySeparator
+                    + "' and sub entity separator '" + subEntitySeparator
+                    + "' have to be different.");
+        }
         indexOfSampleCode =
                 PropertyUtils
                         .getInt(properties, INDEX_OF_SAMPLE_CODE, DEFAULT_INDEX_OF_SAMPLE_CODE);
@@ -223,10 +252,10 @@ public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
         noGroupCode = StringUtils.isBlank(indexAsString);
         indexOfGroupCode = PropertyUtils.getInt(properties, INDEX_OF_GROUP_CODE, 0);
 
-        indexAsString = properties.getProperty(INDEX_OF_PARENT_DATA_SET_CODE);
-        noParentDataSetCode = indexAsString == null;
-        indexOfParentDataSetCode =
-                PropertyUtils.getInt(properties, INDEX_OF_PARENT_DATA_SET_CODE, 0);
+        indexAsString = properties.getProperty(INDEX_OF_PARENT_DATA_SET_CODES);
+        noParentDataSetCodes = indexAsString == null;
+        indexOfParentDataSetCodes =
+                PropertyUtils.getInt(properties, INDEX_OF_PARENT_DATA_SET_CODES, 0);
 
         indexAsString = properties.getProperty(INDEX_OF_DATA_PRODUCER_CODE);
         noDataProducerCode = indexAsString == null;
@@ -257,7 +286,7 @@ public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
                         stripExtension);
         final DataSetInformation dataSetInformation = new DataSetInformation();
         dataSetInformation.setSampleCode(entitiesProvider.getEntity(indexOfSampleCode));
-        dataSetInformation.setParentDataSetCode(tryGetParentDataSetCode(entitiesProvider));
+        dataSetInformation.setParentDataSetCodes(getParentDataSetCodes(entitiesProvider));
         dataSetInformation.setProducerCode(tryGetDataProducerCode(entitiesProvider));
         dataSetInformation.setProductionDate(tryGetDataProductionDate(entitiesProvider));
         dataSetInformation.setDataSetProperties(extractDataSetProperties(incomingDataSetPath,
@@ -266,6 +295,18 @@ public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
         return dataSetInformation;
     }
 
+    private Set<String> getParentDataSetCodes(
+            final DataSetNameEntitiesProvider entitiesProvider)
+    {
+        if (noParentDataSetCodes)
+        {
+            return Collections.emptySet();
+        }
+        String parentDataSetCodes = entitiesProvider.getEntity(indexOfParentDataSetCodes);
+        String[] codes = StringUtils.split(parentDataSetCodes, subEntitySeparator);
+        return new LinkedHashSet<String>(Arrays.asList(codes));
+    }
+
     private String extractGroupCode(DataSetNameEntitiesProvider entitiesProvider)
     {
         if (noGroupCode)
@@ -277,16 +318,6 @@ public class DefaultDataSetInfoExtractor extends AbstractDataSetInfoExtractor
         }
     }
 
-    private String tryGetParentDataSetCode(
-            final DataSetNameEntitiesProvider dataSetNameEntitiesProvider)
-    {
-        if (noParentDataSetCode)
-        {
-            return null;
-        }
-        return dataSetNameEntitiesProvider.getEntity(indexOfParentDataSetCode);
-    }
-
     private String tryGetDataProducerCode(
             final DataSetNameEntitiesProvider dataSetNameEntitiesProvider)
     {
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java
index 649766edb54..2f1a24cb8fe 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java
@@ -25,6 +25,7 @@ import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
+import java.util.Set;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -607,8 +608,12 @@ public final class TransferredDataSetHandler implements IPathHandler, ISelfTesta
                     .getExperimentIdentifier());
             appendNameAndObject(buffer, "Producer Code", dataSetInformation.getProducerCode());
             appendNameAndObject(buffer, "Production Date", dataSetInformation.getProductionDate());
-            appendNameAndObject(buffer, "Parent Data Set", StringUtils
-                    .trimToNull(dataSetInformation.getParentDataSetCode()));
+            Set<String> parentDataSetCodes = dataSetInformation.getParentDataSetCodes();
+            if (parentDataSetCodes.isEmpty() == false)
+            {
+                appendNameAndObject(buffer, "Parent Data Sets", StringUtils.join(
+                        parentDataSetCodes, ' '));
+            }
             appendNameAndObject(buffer, "Is complete", dataSetInformation.getIsCompleteFlag());
             return buffer.toString();
         }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysis.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysis.java
index a3e5d52eeee..2bfaa76aa9f 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysis.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysis.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.etlserver.threev;
 
+import java.util.Collections;
 import java.util.Properties;
 
 import ch.rinn.restrictions.Private;
@@ -32,7 +33,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
  * <li>Data production date
  * </ul>
  * This class uses the same properties as {@link DefaultDataSetInfoExtractor} except
- * <code>index-of-parent-data-set-code</code>. Instead the following properties are used to
+ * <code>index-of-parent-data-set-codes</code>. Instead the following properties are used to
  * extract the parent data set code: <table border="1" cellspacing="0" cellpadding="5">
  * <tr>
  * <th>Property</th>
@@ -77,7 +78,7 @@ public class DataSetInfoExtractorForImageAnalysis extends AbstractDataSetInfoExt
     @Override
     protected void setCodeFor(final DataSetInformation dataSetInfo, final String code)
     {
-        dataSetInfo.setParentDataSetCode(code);
+        dataSetInfo.setParentDataSetCodes(Collections.singleton(code));
     }
 
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetInformation.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetInformation.java
index 899ede0ca06..ae3a448cef8 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetInformation.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetInformation.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.dss.generic.shared.dto;
 import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.commons.lang.builder.ToStringBuilder;
 
@@ -252,14 +253,14 @@ public class DataSetInformation implements Serializable
         this.extractableData = extractableData;
     }
 
-    public final String getParentDataSetCode()
+    public final Set<String> getParentDataSetCodes()
     {
-        return extractableData.getParentDataSetCode();
+        return extractableData.getParentDataSetCodes();
     }
 
-    public final void setParentDataSetCode(final String parentDataSetCode)
+    public final void setParentDataSetCodes(Set<String> parentDataSetCodes)
     {
-        extractableData.setParentDataSetCode(parentDataSetCode);
+        extractableData.setParentDataSetCodes(parentDataSetCodes);
     }
 
     public final void setGroupCode(final String groupCode)
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/CodeExtractortTestCase.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/CodeExtractortTestCase.java
index 7bb879032d5..c3bfec49fec 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/CodeExtractortTestCase.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/CodeExtractortTestCase.java
@@ -25,12 +25,15 @@ public abstract class CodeExtractortTestCase
 
     protected static final String ENTITY_SEPARATOR =
             PREFIX + DefaultDataSetInfoExtractor.ENTITY_SEPARATOR_PROPERTY_NAME;
+    
+    protected static final String SUB_ENTITY_SEPARATOR =
+            PREFIX + DefaultDataSetInfoExtractor.SUB_ENTITY_SEPARATOR_PROPERTY_NAME;
 
     protected static final String INDEX_OF_SAMPLE_CODE =
             PREFIX + DefaultDataSetInfoExtractor.INDEX_OF_SAMPLE_CODE;
 
     protected static final String INDEX_OF_PARENT_DATA_SET_CODE =
-            PREFIX + DefaultDataSetInfoExtractor.INDEX_OF_PARENT_DATA_SET_CODE;
+            PREFIX + DefaultDataSetInfoExtractor.INDEX_OF_PARENT_DATA_SET_CODES;
 
     protected static final String INDEX_OF_DATA_PRODUCER_CODE =
             PREFIX + DefaultDataSetInfoExtractor.INDEX_OF_DATA_PRODUCER_CODE;
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractorTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractorTest.java
index c7cc31417ba..e796ecd7228 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractorTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/DefaultDataSetInfoExtractorTest.java
@@ -65,7 +65,7 @@ public final class DefaultDataSetInfoExtractorTest extends CodeExtractortTestCas
 
         assertNull(dsInfo.getExperimentIdentifier());
         assertEquals(barcode, dsInfo.getSampleIdentifier().getSampleCode());
-        assertEquals(null, dsInfo.getParentDataSetCode());
+        assertEquals(0, dsInfo.getParentDataSetCodes().size());
         assertEquals(null, dsInfo.getProducerCode());
         assertEquals(null, dsInfo.getProductionDate());
     }
@@ -153,6 +153,8 @@ public final class DefaultDataSetInfoExtractorTest extends CodeExtractortTestCas
         final Properties properties = new Properties();
         final String separator = "=";
         properties.setProperty(ENTITY_SEPARATOR, separator);
+        String subSeparator = "%"; 
+        properties.setProperty(SUB_ENTITY_SEPARATOR, subSeparator);
         properties.setProperty(INDEX_OF_SAMPLE_CODE, "0");
         properties.setProperty(INDEX_OF_PARENT_DATA_SET_CODE, "1");
         properties.setProperty(INDEX_OF_DATA_PRODUCER_CODE, "-2");
@@ -162,18 +164,21 @@ public final class DefaultDataSetInfoExtractorTest extends CodeExtractortTestCas
         properties.setProperty(DATA_SET_PROPERTIES_FILE_NAME_KEY, "props.tsv");
         final IDataSetInfoExtractor extractor = new DefaultDataSetInfoExtractor(properties);
         final String producerCode = "M1";
-        final String parentDataSetCode = "1234-8";
+        final String parentDataSet1 = "1234-8";
+        final String parentDataSet2 = "3456-9";
         final String productionDate = "2007-09-03";
         final String barcode = "XYZ-123";
         File incoming =
-                new File(WORKING_DIRECTORY, barcode + separator + parentDataSetCode + separator
-                        + "A" + separator + producerCode + separator + productionDate);
+                new File(WORKING_DIRECTORY, barcode + separator + parentDataSet1 + subSeparator
+                        + parentDataSet2 + separator + "A" + separator + producerCode + separator
+                        + productionDate);
         incoming.mkdir();
         FileUtilities.writeToFile(new File(incoming, "props.tsv"),
                 "property\tvalue\np1\tv1\np2\tv2");
         final DataSetInformation dsInfo = extractor.getDataSetInformation(incoming, null);
         assertEquals(barcode, dsInfo.getSampleIdentifier().getSampleCode());
-        assertEquals(parentDataSetCode, dsInfo.getParentDataSetCode());
+        assertEquals("[" + parentDataSet1 + ", " + parentDataSet2 + "]", dsInfo
+                .getParentDataSetCodes().toString());
         assertEquals(producerCode, dsInfo.getProducerCode());
         final SimpleDateFormat dateFormat = new SimpleDateFormat(format);
         assertEquals(productionDate, dateFormat.format(dsInfo.getProductionDate()));
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 c2622b4fac7..a32de911e65 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
@@ -18,6 +18,7 @@ package ch.systemsx.cisd.etlserver;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Properties;
 
@@ -139,7 +140,7 @@ public final class TransferredDataSetHandlerTest extends AbstractFileSystemTestC
             assertEquals(expectedData.getLocatorType(), data.getLocatorType());
             assertEquals(expectedData.getFileFormatType(), data.getFileFormatType());
             assertEquals(expectedData.getDataSetType(), data.getDataSetType());
-            assertEquals(expectedData.getParentDataSetCode(), data.getParentDataSetCode());
+            assertEquals(expectedData.getParentDataSetCodes(), data.getParentDataSetCodes());
             assertEquals(expectedData.getProductionDate(), data.getProductionDate());
             assertEquals(expectedData.getStorageFormat(), data.getStorageFormat());
             return true;
@@ -280,7 +281,7 @@ public final class TransferredDataSetHandlerTest extends AbstractFileSystemTestC
         dataSetInformation.setProducerCode(DATA_PRODUCER_CODE);
         dataSetInformation.setProductionDate(DATA_PRODUCTION_DATE);
         dataSetInformation.setDataSetCode(DATA_SET_CODE);
-        dataSetInformation.setParentDataSetCode(PARENT_DATA_SET_CODE);
+        dataSetInformation.setParentDataSetCodes(Collections.singleton(PARENT_DATA_SET_CODE));
         targetFolder =
                 IdentifiedDataStrategy.createBaseDirectory(workingDirectory, dataSetInformation);
         targetData1 = createTargetData(data1);
@@ -316,7 +317,7 @@ public final class TransferredDataSetHandlerTest extends AbstractFileSystemTestC
         data.setDataProducerCode(DATA_PRODUCER_CODE);
         data.setProductionDate(DATA_PRODUCTION_DATE);
         data.setCode(DATA_SET_CODE);
-        data.setParentDataSetCode(PARENT_DATA_SET_CODE);
+        data.setParentDataSetCodes(Collections.singleton(PARENT_DATA_SET_CODE));
         return data;
     }
 
@@ -443,9 +444,10 @@ public final class TransferredDataSetHandlerTest extends AbstractFileSystemTestC
             stringBuffer.append("Production Date:\t" + dataset.getProductionDate()
                     + OSUtilities.LINE_SEPARATOR);
         }
-        if (StringUtils.isNotBlank(dataset.getParentDataSetCode()))
+        if (dataset.getParentDataSetCodes().isEmpty() == false)
         {
-            stringBuffer.append("Parent Data Set:\t" + dataset.getParentDataSetCode()
+            stringBuffer.append("Parent Data Sets:\t"
+                    + StringUtils.join(dataset.getParentDataSetCodes(), ' ')
                     + OSUtilities.LINE_SEPARATOR);
         }
         stringBuffer.append("Is complete:\t" + dataset.getIsCompleteFlag()
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForDataAcquisitionTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForDataAcquisitionTest.java
index 1d02b7fa035..3c661f9a31d 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForDataAcquisitionTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForDataAcquisitionTest.java
@@ -53,7 +53,7 @@ public class DataSetInfoExtractorForDataAcquisitionTest extends CodeExtractortTe
                 extractor.getDataSetInformation(new File("alpha.42.beta"), null);
         assertEquals("42.alpha", dataSetInfo.getDataSetCode());
         assertEquals("beta", dataSetInfo.getSampleIdentifier().getSampleCode());
-        assertEquals(null, dataSetInfo.getParentDataSetCode());
+        assertEquals(0, dataSetInfo.getParentDataSetCodes().size());
         assertEquals(null, dataSetInfo.getProducerCode());
         assertEquals(null, dataSetInfo.getProductionDate());
     }
@@ -79,7 +79,8 @@ public class DataSetInfoExtractorForDataAcquisitionTest extends CodeExtractortTe
                 extractor.getDataSetInformation(new File("a_b_c_" + date), null);
         assertEquals("2007-12-24-c", dataSetInfo.getDataSetCode());
         assertEquals("a", dataSetInfo.getSampleIdentifier().getSampleCode());
-        assertEquals("b", dataSetInfo.getParentDataSetCode());
+        assertEquals(1, dataSetInfo.getParentDataSetCodes().size());
+        assertEquals("b", dataSetInfo.getParentDataSetCodes().iterator().next());
         assertEquals("c", dataSetInfo.getProducerCode());
         assertEquals(date, new SimpleDateFormat(dateFormat).format(dataSetInfo.getProductionDate()));
     }
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysisTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysisTest.java
index e2d788b34c9..2680989d431 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysisTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/threev/DataSetInfoExtractorForImageAnalysisTest.java
@@ -46,7 +46,8 @@ public class DataSetInfoExtractorForImageAnalysisTest extends CodeExtractortTest
 
         DataSetInformation dataSetInfo =
                 extractor.getDataSetInformation(new File("alpha.42.beta"), null);
-        assertEquals("42.alpha", dataSetInfo.getParentDataSetCode());
+        assertEquals(1, dataSetInfo.getParentDataSetCodes().size());
+        assertEquals("42.alpha", dataSetInfo.getParentDataSetCodes().iterator().next());
         assertEquals("beta", dataSetInfo.getSampleIdentifier().getSampleCode());
         assertEquals(null, dataSetInfo.getDataSetCode());
         assertEquals(null, dataSetInfo.getProducerCode());
-- 
GitLab