From f622fc76ef965bd2349b64d5131e5f439b2e86c5 Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Thu, 12 Nov 2015 07:58:49 +0000
Subject: [PATCH] SSDM-2601: Project sample identifiers support for retrieving
 samples and updating samples. More tests in ProjectSampleTest. Making
 ProjectSampleTest more robust. ListSampleByIdentifier refactored by using
 ListSampleTechIdByIdentifier. Revert extracted static method done in
 ListSampleTechIdByIdentifier.

SVN: 35044
---
 .../executor/sample/FullSampleIdentifier.java |  83 +++++---
 .../sample/ListSampleTechIdByIdentifier.java  |  69 +++----
 .../sample/MapSampleByIdExecutor.java         |   9 +-
 .../sample/SampleIdentifierParts.java         |   9 +-
 .../api/v3/executor/sample/SampleQuery.java   |  21 +-
 .../helper/sample/ListSampleByIdentifier.java | 191 +-----------------
 .../sample/FullSampleIdentifierTest.java      |  37 +++-
 .../systemtest/api/v3/ProjectSampleTest.java  | 140 +++++++------
 8 files changed, 229 insertions(+), 330 deletions(-)

diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifier.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifier.java
index ba2b50955a1..441695289f1 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifier.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifier.java
@@ -16,6 +16,10 @@
 
 package ch.ethz.sis.openbis.generic.server.api.v3.executor.sample;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
 import org.apache.commons.lang.StringUtils;
 
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SampleIdentifier;
@@ -26,7 +30,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SampleIdentifier;
  * 
  * @author Franz-Josef Elmer
  */
-class FullSampleIdentifier
+public class FullSampleIdentifier
 {
     private static final String CODE_CHAR_PATTERN = "a-zA-Z0-9_\\-\\.";
     private static final String CODE_CHARS = "[" + CODE_CHAR_PATTERN + "]+";
@@ -35,7 +39,7 @@ class FullSampleIdentifier
     private SampleIdentifierParts sampleIdentifierParts;
     private String sampleCode;
     
-    FullSampleIdentifier(String sampleIdentifier)
+    public FullSampleIdentifier(String sampleIdentifier, String homeSpaceCodeOrNull)
     {
         String[] parts = extractParts(sampleIdentifier);
         String spaceCode = null;
@@ -56,35 +60,58 @@ class FullSampleIdentifier
             projectCode = parts[2];
             code = parts[3];
         }
+        if (spaceCode != null && spaceCode.isEmpty() && homeSpaceCodeOrNull != null)
+        {
+            spaceCode = homeSpaceCodeOrNull;
+        }
         
-        String[] splittedCode = splitCode(code, sampleIdentifier);
-        if (splittedCode.length == 2)
+        List<String> splittedCode = splitCode(code, sampleIdentifier);
+        if (splittedCode.size() == 2)
         {
-            containerCode = splittedCode[0];
-            plainSampleCode = splittedCode[1];
+            containerCode = splittedCode.get(0);
+            plainSampleCode = splittedCode.get(1);
         } else {
-            plainSampleCode = splittedCode[0];
+            plainSampleCode = splittedCode.get(0);
         }
         
         //Code format validation
-        verifyCodePattern(spaceCode);
-        verifyCodePattern(projectCode);
-        verifyCodePattern(containerCode);
-        verifyCodePattern(plainSampleCode);
+        verifyCodePattern(spaceCode, "Space code");
+        verifyCodePattern(projectCode, "Project code");
+        verifyCodePattern(containerCode, "Container sample code");
+        verifyCodePattern(plainSampleCode, containerCode == null ? "Sample code" : "Sample subcode");
             
         sampleCode = plainSampleCode;
         sampleIdentifierParts = new SampleIdentifierParts(spaceCode, projectCode, containerCode);
         
     }
 
-    private String[] splitCode(String code, String sampleIdentifier)
+    private List<String> splitCode(String code, String sampleIdentifier)
     {
-        String[] splittedCode = code.split(":");
-        if (splittedCode.length > 2)
+        String delim = ":";
+        StringTokenizer tokenizer = new StringTokenizer(code, delim, true);
+        int numberOfDelims = 0;
+        List<String> tokens = new ArrayList<>(); 
+        while (tokenizer.hasMoreTokens())
+        {
+            String token = tokenizer.nextToken();
+            if (delim.equals(token))
+            {
+                numberOfDelims++;
+            } else
+            {
+                tokens.add(token);
+            }
+        }
+        if (numberOfDelims > 1)
+        {
+            throw new IllegalArgumentException("Sample code can not contain more than one '" + delim + "': " 
+                    + sampleIdentifier);
+        }
+        if (numberOfDelims != tokens.size() - 1)
         {
-            throw new IllegalArgumentException("Sample code can not contain more than one ':': " + sampleIdentifier);
+            throw new IllegalArgumentException("Sample code starts or ends with '" + delim + "': " + sampleIdentifier);
         }
-        return splittedCode;
+        return tokens;
     }
 
     private String[] extractParts(String sampleIdentifier)
@@ -110,19 +137,29 @@ class FullSampleIdentifier
         return parts;
     }
 
-    private void verifyCodePattern(String code) {
-        if(code != null && code.matches(CODE_PATTERN) == false) {
-            throw new IllegalArgumentException("Code field containing other characters than letters, numbers, '_', '-' and '.': " + code);
+    private void verifyCodePattern(String code, String partName)
+    {
+        if (code == null)
+        {
+            return;
         }
-    }
-    
+        if (code.length() == 0)
+        {
+            throw new IllegalArgumentException(partName + " can not be an empty string.");
+        }
+        if (code.matches(CODE_PATTERN) == false)
+        {
+            throw new IllegalArgumentException(partName + " containing other characters than letters, numbers, "
+                    + "'_', '-' and '.': " + code);
+        }
+    }    
     
-    SampleIdentifierParts getParts()
+    public SampleIdentifierParts getParts()
     {
         return sampleIdentifierParts;
     }
 
-    String getSampleCode()
+    public String getSampleCode()
     {
         return sampleCode;
     }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java
index 366cf9f11f0..a1fd1e470f1 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java
@@ -27,8 +27,6 @@ import net.lemnik.eodsql.QueryTool;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.common.TechIdStringIdentifierRecord;
 import ch.ethz.sis.openbis.generic.server.api.v3.helper.common.AbstractListTechIdById;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SampleIdentifier;
-import ch.systemsx.cisd.openbis.generic.shared.basic.CodeConverter;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory;
 
 /**
  * 
@@ -53,37 +51,7 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
     @Override
     protected Map<Long, SampleIdentifier> createIdsByTechIdsMap(List<SampleIdentifier> ids)
     {
-        Map<SampleIdentifierParts, Map<String, SampleIdentifier>> groupedIdentifiers = new HashMap<>();
-        for (SampleIdentifier sampleIdentifier : ids)
-        {
-            ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier sampleIdentifier2 
-                = SampleIdentifierFactory.parse(sampleIdentifier.getIdentifier());
-            String spaceCode = null;
-            if (sampleIdentifier2.isSpaceLevel())
-            {
-                if (sampleIdentifier2.isInsideHomeSpace())
-                {
-                    if (homeSpaceCodeOrNull == null)
-                    {
-                        continue;
-                    }
-                    spaceCode = homeSpaceCodeOrNull;
-                } else
-                {
-                    spaceCode = CodeConverter.tryToDatabase(sampleIdentifier2.getSpaceLevel().getSpaceCode());
-                }
-            }
-            String sampleSubCode = CodeConverter.tryToDatabase(sampleIdentifier2.getSampleSubCode());
-            String containerCode = CodeConverter.tryToDatabase(sampleIdentifier2.tryGetContainerCode());
-            SampleIdentifierParts key = new SampleIdentifierParts(spaceCode, null, containerCode);
-            Map<String, SampleIdentifier> identifiersByCode = groupedIdentifiers.get(key);
-            if (identifiersByCode == null)
-            {
-                identifiersByCode = new HashMap<>();
-                groupedIdentifiers.put(key, identifiersByCode);
-            }
-            identifiersByCode.put(sampleSubCode, sampleIdentifier);
-        }
+        Map<SampleIdentifierParts, Map<String, SampleIdentifier>> groupedIdentifiers = groupIdentifiers(ids);
         
         Map<Long, SampleIdentifier> result = new HashMap<>();
         SampleQuery query = QueryTool.getManagedQuery(SampleQuery.class);
@@ -100,11 +68,32 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
         }
         return result;
     }
-    
+
+    private Map<SampleIdentifierParts, Map<String, SampleIdentifier>> groupIdentifiers(List<SampleIdentifier> ids)
+    {
+        Map<SampleIdentifierParts, Map<String, SampleIdentifier>> groupedIdentifiers = new HashMap<>();
+        for (SampleIdentifier sampleIdentifier : ids)
+        {
+            FullSampleIdentifier fullSampleIdentifier = new FullSampleIdentifier(sampleIdentifier.getIdentifier(), 
+                    homeSpaceCodeOrNull);
+            
+            SampleIdentifierParts key = fullSampleIdentifier.getParts();
+            Map<String, SampleIdentifier> identifiersByCode = groupedIdentifiers.get(key);
+            if (identifiersByCode == null)
+            {
+                identifiersByCode = new HashMap<>();
+                groupedIdentifiers.put(key, identifiersByCode);
+            }
+            identifiersByCode.put(fullSampleIdentifier.getSampleCode(), sampleIdentifier);
+        }
+        return groupedIdentifiers;
+    }
+
     private List<TechIdStringIdentifierRecord> list(SampleQuery query, SampleIdentifierParts key, Collection<String> codes)
     {
         String[] codesArray = codes.toArray(new String[codes.size()]);
         String spaceCode = key.getSpaceCodeOrNull();
+        String projectCode = key.getProjectCodeOrNull();
         String containerCode = key.getContainerCodeOrNull();
         if (spaceCode == null)
         {
@@ -114,11 +103,19 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
             }
             return query.listSharedSampleTechIdsByContainerCodeAndCodes(containerCode, codesArray);
         }
+        if (projectCode == null)
+        {
+            if (containerCode == null)
+            {
+                return query.listSpaceSampleTechIdsByCodes(spaceCode, codesArray);
+            }
+            return query.listSpaceSampleTechIdsByContainerCodeAndCodes(spaceCode, containerCode, codesArray);
+        }
         if (containerCode == null)
         {
-            return query.listSpaceSampleTechIdsByCodes(spaceCode, codesArray);
+            return query.listProjectSampleTechIdsByCodes(spaceCode, projectCode, codesArray);
         }
-        return query.listSpaceSampleTechIdsByContainerCodeAndCodes(spaceCode, containerCode, codesArray);
+        return query.listProjectSampleTechIdsByContainerCodeAndCodes(spaceCode, projectCode, containerCode, codesArray);
     }
 
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/MapSampleByIdExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/MapSampleByIdExecutor.java
index 7b3cda71f60..b521033dd4a 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/MapSampleByIdExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/MapSampleByIdExecutor.java
@@ -29,8 +29,8 @@ import ch.ethz.sis.openbis.generic.server.api.v3.helper.sample.ListSampleByPermI
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.ISampleId;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISampleDAO;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISpaceDAO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
 
 /**
  * @author pkupczyk
@@ -38,22 +38,19 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 @Component
 public class MapSampleByIdExecutor extends AbstractMapObjectByIdExecutor<ISampleId, SamplePE> implements IMapSampleByIdExecutor
 {
-
-    private ISpaceDAO spaceDAO;
-
     private ISampleDAO sampleDAO;
 
     @Override
     protected void addListers(IOperationContext context, List<IListObjectById<? extends ISampleId, SamplePE>> listers)
     {
         listers.add(new ListSampleByPermId(sampleDAO));
-        listers.add(new ListSampleByIdentifier(spaceDAO, sampleDAO, context.getSession().tryGetHomeGroup()));
+        SpacePE space = context.getSession().tryGetHomeGroup();
+        listers.add(new ListSampleByIdentifier(sampleDAO, space));
     }
 
     @Autowired
     private void setDAOFactory(IDAOFactory daoFactory)
     {
-        spaceDAO = daoFactory.getSpaceDAO();
         sampleDAO = daoFactory.getSampleDAO();
     }
 
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleIdentifierParts.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleIdentifierParts.java
index 61302519b06..35e1fd85e35 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleIdentifierParts.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleIdentifierParts.java
@@ -16,7 +16,7 @@
 
 package ch.ethz.sis.openbis.generic.server.api.v3.executor.sample;
 
-final class SampleIdentifierParts
+public final class SampleIdentifierParts
 {
     private String spaceCodeOrNull;
     private String projectCodeOrNull;
@@ -73,13 +73,6 @@ final class SampleIdentifierParts
                 + calcHashCode(containerCodeOrNull);
     }
     
-    @Override
-    public String toString()
-    {
-        // TODO Auto-generated method stub
-        return super.toString();
-    }
-
     private int calcHashCode(String str)
     {
         return str == null ? 0 : str.hashCode();
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleQuery.java
index d1e0f3727ab..034ba69d0db 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleQuery.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleQuery.java
@@ -48,14 +48,29 @@ public interface SampleQuery extends ObjectQuery
             String[] codes);
     
     @Select(sql = "select s.id, s.code as identifier from samples s join spaces sp on s.space_id = sp.id "
-            + "where sp.code = ?{1} and samp_id_part_of is null and s.code = any(?{2})", parameterBindings =
-            { TypeMapper.class, StringArrayMapper.class }, fetchSize = FETCH_SIZE)
+            + "where sp.code = ?{1} and proj_id is null and samp_id_part_of is null and s.code = any(?{2})", 
+            parameterBindings = { TypeMapper.class, StringArrayMapper.class }, fetchSize = FETCH_SIZE)
     public List<TechIdStringIdentifierRecord> listSpaceSampleTechIdsByCodes(String spaceCode, String[] codes);
     
     @Select(sql = "select s.id, s.code as identifier from samples s join spaces sp on s.space_id = sp.id "
             + "join samples cs on s.samp_id_part_of = cs.id "
-            + "where sp.code = ?{1}  and cs.code = ?{2} and s.code = any(?{3})", parameterBindings =
+            + "where sp.code = ?{1} and proj_id is null and cs.code = ?{2} and s.code = any(?{3})", parameterBindings =
         { TypeMapper.class, TypeMapper.class, StringArrayMapper.class }, fetchSize = FETCH_SIZE)
     public List<TechIdStringIdentifierRecord> listSpaceSampleTechIdsByContainerCodeAndCodes(String spaceCode, 
             String containerCode, String[] codes);
+    
+    @Select(sql = "select s.id, s.code as identifier from samples s join spaces sp on s.space_id = sp.id "
+            + "join projects p on s.proj_id = p.id "
+            + "where sp.code = ?{1} and samp_id_part_of is null and p.code = ?{2} and s.code = any(?{3})", parameterBindings =
+        { TypeMapper.class, TypeMapper.class, StringArrayMapper.class }, fetchSize = FETCH_SIZE)
+    public List<TechIdStringIdentifierRecord> listProjectSampleTechIdsByCodes(String spaceCode, 
+            String projectCode, String[] codes);
+    
+    @Select(sql = "select s.id, s.code as identifier from samples s join spaces sp on s.space_id = sp.id "
+            + "join projects p on s.proj_id = p.id "
+            + "join samples cs on s.samp_id_part_of = cs.id "
+            + "where sp.code = ?{1} and p.code = ?{2} and cs.code = ?{3} and s.code = any(?{4})", parameterBindings =
+        { TypeMapper.class, TypeMapper.class, TypeMapper.class, StringArrayMapper.class }, fetchSize = FETCH_SIZE)
+    public List<TechIdStringIdentifierRecord> listProjectSampleTechIdsByContainerCodeAndCodes(String spaceCode, 
+            String projectCode, String containerCode, String[] codes);
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/helper/sample/ListSampleByIdentifier.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/helper/sample/ListSampleByIdentifier.java
index d98f8f55342..e87fde0892b 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/helper/sample/ListSampleByIdentifier.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/helper/sample/ListSampleByIdentifier.java
@@ -16,24 +16,14 @@
 
 package ch.ethz.sis.openbis.generic.server.api.v3.helper.sample;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
 
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.sample.ListSampleTechIdByIdentifier;
 import ch.ethz.sis.openbis.generic.server.api.v3.helper.common.AbstractListObjectById;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SampleIdentifier;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISampleDAO;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISpaceDAO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 
 /**
  * @author Franz-Josef Elmer
@@ -42,17 +32,15 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 public class ListSampleByIdentifier extends AbstractListObjectById<SampleIdentifier, SamplePE>
 {
 
-    private ISpaceDAO spaceDAO;
-
     private ISampleDAO sampleDAO;
 
-    private SpacePE homeSpaceOrNull;
+    private ListSampleTechIdByIdentifier techIdByIdentifier;
 
-    public ListSampleByIdentifier(ISpaceDAO spaceDAO, ISampleDAO sampleDAO, SpacePE homeSpaceOrNull)
+    public ListSampleByIdentifier(ISampleDAO sampleDAO, SpacePE homeSpaceOrNull)
     {
-        this.spaceDAO = spaceDAO;
         this.sampleDAO = sampleDAO;
-        this.homeSpaceOrNull = homeSpaceOrNull;
+        String homeSpaceCodeOrNull = homeSpaceOrNull == null ? null : homeSpaceOrNull.getCode();
+        techIdByIdentifier = new ListSampleTechIdByIdentifier(homeSpaceCodeOrNull);
     }
 
     @Override
@@ -64,178 +52,13 @@ public class ListSampleByIdentifier extends AbstractListObjectById<SampleIdentif
     @Override
     public SampleIdentifier createId(SamplePE sample)
     {
-        return new SampleIdentifier(sample.getIdentifier());
+        return techIdByIdentifier.createId(sample.getId());
     }
 
     @Override
     public List<SamplePE> listByIds(List<SampleIdentifier> ids)
     {
-        Map<SampleIdentifier, ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier> idMap = getIdMap(ids);
-        Map<String, SpacePE> spaceMap = getSpaceMap(idMap.values());
-
-        Map<Key, List<String>> sampleCodesBySpaceAndContainer = new HashMap<ListSampleByIdentifier.Key, List<String>>();
-
-        for (SampleIdentifier id : ids)
-        {
-            ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier sid = idMap.get(id);
-
-            if (sid.isDatabaseInstanceLevel())
-            {
-                addToMap(sampleCodesBySpaceAndContainer, null, sid);
-            } else if (sid.isSpaceLevel())
-            {
-                SpaceIdentifier spaceIdentifier = sid.getSpaceLevel();
-                if (sid.isInsideHomeSpace() == false)
-                {
-                    String spaceCode = spaceIdentifier.getSpaceCode();
-                    SpacePE space = spaceMap.get(spaceCode);
-                    if (space != null)
-                    {
-                        addToMap(sampleCodesBySpaceAndContainer, space, sid);
-                    }
-                } else if (homeSpaceOrNull != null)
-                {
-                    addToMap(sampleCodesBySpaceAndContainer, homeSpaceOrNull, sid);
-                }
-            } else
-            {
-                assert false;
-            }
-        }
-        List<SamplePE> result = new ArrayList<SamplePE>();
-        Set<Entry<Key, List<String>>> entrySet = sampleCodesBySpaceAndContainer.entrySet();
-
-        for (Entry<Key, List<String>> entry : entrySet)
-        {
-            Key key = entry.getKey();
-            List<String> sampleCodes = entry.getValue();
-            SpacePE space = key.getSpace();
-            String containerId = key.getContainerId();
-            if (space == null)
-            {
-                result.addAll(sampleDAO.listByCodesAndDatabaseInstance(sampleCodes, containerId));
-            } else
-            {
-                result.addAll(sampleDAO.listByCodesAndSpace(sampleCodes, containerId, space));
-            }
-        }
-        return result;
-    }
-
-    private Map<SampleIdentifier, ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier> getIdMap(List<SampleIdentifier> ids)
-    {
-        Map<SampleIdentifier, ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier> sidMap =
-                new HashMap<SampleIdentifier, ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier>();
-
-        for (SampleIdentifier id : ids)
-        {
-            ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier sid = SampleIdentifierFactory.parse(id.getIdentifier());
-            sidMap.put(id, sid);
-        }
-
-        return sidMap;
-    }
-
-    private Map<String, SpacePE> getSpaceMap(Collection<ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier> ids)
-    {
-        Set<String> spaceCodes = new HashSet<String>();
-
-        for (ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier id : ids)
-        {
-            if (id.isSpaceLevel())
-            {
-                spaceCodes.add(id.getSpaceLevel().getSpaceCode());
-            }
-        }
-
-        if (false == spaceCodes.isEmpty())
-        {
-            List<SpacePE> spaces = spaceDAO.tryFindSpaceByCodes(new ArrayList<String>(spaceCodes));
-            Map<String, SpacePE> spaceMap = new HashMap<String, SpacePE>();
-
-            for (SpacePE space : spaces)
-            {
-                spaceMap.put(space.getCode(), space);
-            }
-
-            return spaceMap;
-        } else
-        {
-            return Collections.emptyMap();
-        }
-    }
-
-    private void addToMap(Map<Key, List<String>> sampleCodesBySpaceAndContainer, SpacePE space,
-            ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier sampleIdentifier)
-    {
-        Key key = new Key(space, sampleIdentifier.tryGetContainerCode());
-        List<String> list = sampleCodesBySpaceAndContainer.get(key);
-        if (list == null)
-        {
-            list = new ArrayList<String>();
-            sampleCodesBySpaceAndContainer.put(key, list);
-        }
-        list.add(sampleIdentifier.getSampleSubCode());
-    }
-
-    private static final class Key
-    {
-        final SpacePE space;
-
-        final String containerId;
-
-        public Key(SpacePE space, String containerId)
-        {
-            super();
-            this.space = space;
-            this.containerId = containerId;
-        }
-
-        public SpacePE getSpace()
-        {
-            return space;
-        }
-
-        public String getContainerId()
-        {
-            return containerId;
-        }
-
-        @Override
-        public int hashCode()
-        {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + ((containerId == null) ? 0 : containerId.hashCode());
-            result = prime * result + ((space == null) ? 0 : space.hashCode());
-            return result;
-        }
-
-        @Override
-        public boolean equals(Object obj)
-        {
-            if (this == obj)
-                return true;
-            if (obj == null)
-                return false;
-            if (getClass() != obj.getClass())
-                return false;
-            Key other = (Key) obj;
-            if (containerId == null)
-            {
-                if (other.containerId != null)
-                    return false;
-            } else if (!containerId.equals(other.containerId))
-                return false;
-            if (space == null)
-            {
-                if (other.space != null)
-                    return false;
-            } else if (!space.equals(other.space))
-                return false;
-            return true;
-        }
-
+        return sampleDAO.listByIDs(techIdByIdentifier.listByIds(ids));
     }
 
 }
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifierTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifierTest.java
index a0e592afda4..f74dfa8e1e1 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifierTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifierTest.java
@@ -33,11 +33,15 @@ public class FullSampleIdentifierTest
     public void testHappyCases()
     {
         assertSampId("/S1");
+        assertSampId("/C1:S1");
         assertSampId("/SPACE1/S2");
         assertSampId("/SPACE1/S2:A02");
         assertSampId("/SPACE1/PROJECT1/S1");
         assertSampId("/SPACE1/PROJECT1/S1:A02");
-        
+        assertSampIdWithHomeSpace("/HS/S1", "//S1", "HS");
+        assertSampIdWithHomeSpace("/HS/C1:S1", "//C1:S1", "HS");
+        assertSampIdWithHomeSpace("/HS/PROJECT1/S1", "//PROJECT1/S1", "HS");
+        assertSampIdWithHomeSpace("/HS/PROJECT1/C1:S1", "//PROJECT1/C1:S1", "HS");
     }
     
     @Test
@@ -53,18 +57,30 @@ public class FullSampleIdentifierTest
         assertInvalidSampId("Sample identifier don't contain any codes: //", "//");
         assertInvalidSampId("Sample identifier don't contain any codes: /", "/");
         
-        assertInvalidSampId("Code field containing other characters than letters, numbers, '_', '-' and '.': S1&*", "/S1&*");
-        assertInvalidSampId("Code field containing other characters than letters, numbers, '_', '-' and '.': SPA&CE1", "/SPA&CE1/S2");
-        assertInvalidSampId("Code field containing other characters than letters, numbers, '_', '-' and '.': S^2", "/SPACE1/S^2:A02");
-        assertInvalidSampId("Code field containing other characters than letters, numbers, '_', '-' and '.': PRO<>JECT1", "/SPACE1/PRO<>JECT1/S1");
-        assertInvalidSampId("Code field containing other characters than letters, numbers, '_', '-' and '.': A0(2", "/SPACE1/PROJECT1/S1:A0(2");
+        assertInvalidSampId("Space code can not be an empty string.", "//S1");
+        assertInvalidSampId("Project code can not be an empty string.", "/S//S1");
+        assertInvalidSampId("Sample code starts or ends with ':': /S/:S1", "/S/:S1");
+        assertInvalidSampId("Sample code starts or ends with ':': /S/C1:", "/S/C1:");
+        assertInvalidSampId("Sample code can not contain more than one ':': /S/C1:S1:", "/S/C1:S1:");
+        
+        String prefix = " containing other characters than letters, numbers, '_', '-' and '.': ";
+        assertInvalidSampId("Sample code" + prefix + "S1&*", "/S1&*");
+        assertInvalidSampId("Space code" + prefix + "SPA&CE1", "/SPA&CE1/S2");
+        assertInvalidSampId("Container sample code" + prefix + "S^2", "/SPACE1/S^2:A02");
+        assertInvalidSampId("Project code" + prefix + "PRO<>JECT1", "/SPACE1/PRO<>JECT1/S1");
+        assertInvalidSampId("Sample subcode" + prefix + "A0(2", "/SPACE1/PROJECT1/S1:A0(2");
     }
     
     private void assertInvalidSampId(String expectedErrorMsg, String identifier)
+    {
+        assertInvalidSampId(expectedErrorMsg, identifier, null);
+    }
+    
+    private void assertInvalidSampId(String expectedErrorMsg, String identifier, String homeSpaceOrNull)
     {
         try
         {
-            new FullSampleIdentifier(identifier);
+            new FullSampleIdentifier(identifier, homeSpaceOrNull);
             fail("IllegalArgumentException expected");
         } catch (IllegalArgumentException ex)
         {
@@ -74,7 +90,12 @@ public class FullSampleIdentifierTest
     
     private void assertSampId(String identifier)
     {
-        assertEquals(new FullSampleIdentifier(identifier).toString(), identifier);
+        assertEquals(new FullSampleIdentifier(identifier, null).toString(), identifier);
+    }
+    
+    private void assertSampIdWithHomeSpace(String expectedIdentifier, String identifier, String homeSpace)
+    {
+        assertEquals(new FullSampleIdentifier(identifier, homeSpace).toString(), expectedIdentifier);
     }
 
 }
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java
index 552841cce64..04e6bdcde47 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java
@@ -34,7 +34,6 @@ import org.springframework.test.context.transaction.TransactionConfiguration;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.shared.api.v3.IApplicationServerApi;
@@ -48,11 +47,11 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.space.SpaceCreation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.project.ProjectFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.sample.SampleFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.entitytype.EntityTypePermId;
-import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.ExperimentPermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.IExperimentId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.project.IProjectId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.project.ProjectPermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.ISampleId;
+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.ISpaceId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.space.SpacePermId;
@@ -74,18 +73,8 @@ import ch.systemsx.cisd.openbis.systemtest.base.BaseTest;
 @Test(groups = "project-samples")
 public class ProjectSampleTest extends BaseTest
 {
-    private static final String[] SEARCH_METHODS 
-        = {"testSearchForSamplesWithProject", "testSearchForSamplesWithCodeAndWithProject"};
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, ProjectSampleTest.class);
     private static final EntityTypePermId ENTITY_TYPE_UNKNOWN = new EntityTypePermId("UNKNOWN");
-    private static final String TEST_USER = "test";
-    private static final String PASSWORD = "password";
-
-    private static final String SYSTEM_USER = "system";
-    private static final String NOT_EXISTING_USER = "notexistinguser";
-    private static final String TEST_SPACE_USER = "test_space";
-    private static final String TEST_POWER_USER_CISD = "test_role";
-    private static final String TEST_GROUP_OBSERVER = "observer";
     
     @Autowired
     protected IApplicationServerApi v3api;
@@ -96,11 +85,6 @@ public class ProjectSampleTest extends BaseTest
     private ProjectPermId project2inSpace1;
     private ProjectPermId project1InSpace2;
     private ProjectPermId project2InSpace2;
-    private ExperimentPermId experimentInProject1InSpace1;
-    private SamplePermId sharedSample1;
-    private SamplePermId sharedSample2;
-    private SamplePermId sample1InSpace1;
-    private SamplePermId sample2InSpace1;
     
     @BeforeClass
     public void createData()
@@ -113,13 +97,8 @@ public class ProjectSampleTest extends BaseTest
         project2inSpace1 = projects.get(1);
         project1InSpace2 = createProjects(systemSessionToken, space2, "PROJECT1").get(0);
         project2InSpace2 = createProjects(systemSessionToken, space2, "PROJECT2").get(0);
-        experimentInProject1InSpace1 = createExperiments(systemSessionToken, project1inSpace1, "EXP1").get(0);
-        List<SamplePermId> sharedSamples = createSamples(systemSessionToken, null, null, null, "SHARED1", "SHARED2");
-        sharedSample1 = sharedSamples.get(0);
-        sharedSample2 = sharedSamples.get(1);
-        List<SamplePermId> spaceSamples = createSamples(systemSessionToken, space1, null, null, "SAMPLE1", "SAMPLE2");
-        sample1InSpace1 = spaceSamples.get(0);
-        sample2InSpace1 = spaceSamples.get(1);
+        createSamples(systemSessionToken, null, null, null, "SHARED1", "SHARED2");
+        createSamples(systemSessionToken, space1, null, null, "SAMPLE1", "SAMPLE2");
         createSamples(systemSessionToken, space1, project1inSpace1, null, "SAMPLE3", "SAMPLE4");
         createSamples(systemSessionToken, space2, project2InSpace2, null, "SAMPLE5", "SAMPLE6");
         waitAtLeastASecond(); // to allow checks on modification time stamps 
@@ -131,6 +110,7 @@ public class ProjectSampleTest extends BaseTest
     @Test(enabled = false)
     public void cleanDatabase()
     {
+        // super method deletes samples, experiments and data sets from the database
     }
 
     private List<SpacePermId> createSpaces(String sessionToken, String...spaceCodes)
@@ -158,20 +138,6 @@ public class ProjectSampleTest extends BaseTest
         return v3api.createProjects(sessionToken, newProjects);
     }
     
-    private List<ExperimentPermId> createExperiments(String sessionToken, IProjectId projectId, String...experimentCodes)
-    {
-        List<ExperimentCreation> newExperiments = new ArrayList<ExperimentCreation>();
-        for (String code : experimentCodes)
-        {
-            ExperimentCreation experiment = new ExperimentCreation();
-            experiment.setTypeId(ENTITY_TYPE_UNKNOWN);
-            experiment.setProjectId(projectId);
-            experiment.setCode(code);
-            newExperiments.add(experiment);
-        }
-        return v3api.createExperiments(sessionToken, newExperiments);
-    }
-    
     private List<SamplePermId> createSamples(String sessionToken, ISpaceId spaceOrNull, 
             IProjectId projectOrNull, IExperimentId experimentOrNull, String...codes)
     {
@@ -190,9 +156,9 @@ public class ProjectSampleTest extends BaseTest
     }
     
     @Test
-    public void testCreateSampleAndMapSamples()
+    public void testCreateSampleAndMapSamplesByPermId()
     {
-        String sampleCode = createUniqueCode();
+        String sampleCode = createUniqueCode("S");
         SampleCreation sampleCreation = new SampleCreation();
         sampleCreation.setCode(sampleCode);
         sampleCreation.setTypeId(ENTITY_TYPE_UNKNOWN);
@@ -208,10 +174,11 @@ public class ProjectSampleTest extends BaseTest
         assertEquals(sample.getIdentifier().getIdentifier(), "/SPACE1/PROJECT1/" + sampleCode);
         assertEquals(sample.getProject().getIdentifier().getIdentifier(), "/SPACE1/PROJECT1");
     }
+    
     @Test
-    public void testCreateThreeSamplesWithSameCodeInDifferentProjectOfSameSpace()
+    public void testCreateThreeSamplesWithSameCodeInDifferentProjectOfSameSpaceAndMapSamplesByIdentifier()
     {
-        String sampleCode = createUniqueCode();
+        String sampleCode = createUniqueCode("S");
         SampleCreation s1 = new SampleCreation();
         s1.setCode(sampleCode);
         s1.setTypeId(ENTITY_TYPE_UNKNOWN);
@@ -227,23 +194,56 @@ public class ProjectSampleTest extends BaseTest
         s3.setTypeId(ENTITY_TYPE_UNKNOWN);
         s3.setSpaceId(space1);
         
-        List<SamplePermId> ids = v3api.createSamples(systemSessionToken, Arrays.asList(s1, s2, s3));
+        v3api.createSamples(systemSessionToken, Arrays.asList(s1, s2, s3));
         
         SampleFetchOptions fetchOptions = new SampleFetchOptions();
         fetchOptions.withProject();
+        List<ISampleId> ids = new ArrayList<>();
+        ids.add(new SampleIdentifier("/SPACE1/PROJECT1/" + sampleCode));
+        ids.add(new SampleIdentifier("/SPACE1/PROJECT2/" + sampleCode));
+        ids.add(new SampleIdentifier("/SPACE1/" + sampleCode));
         Map<ISampleId, Sample> samples = v3api.mapSamples(systemSessionToken, ids, fetchOptions);
         assertEquals(samples.get(ids.get(0)).getProject().getIdentifier().toString(), "/SPACE1/PROJECT1");
         assertEquals(samples.get(ids.get(1)).getProject().getIdentifier().toString(), "/SPACE1/PROJECT2");
         assertEquals(samples.get(ids.get(2)).getProject(), null);
     }
+    
+    @Test
+    public void testCreateProjectSampleWithComponent()
+    {
+        String sampleCode = createUniqueCode("S");
+        SampleCreation s1 = new SampleCreation();
+        s1.setCode(sampleCode);
+        s1.setTypeId(ENTITY_TYPE_UNKNOWN);
+        s1.setSpaceId(space1);
+        s1.setProjectId(project1inSpace1);
+        SamplePermId s1PermId = v3api.createSamples(systemSessionToken, Arrays.asList(s1)).get(0);
+        SampleCreation s2 = new SampleCreation();
+        s2.setCode("A01");
+        s2.setTypeId(ENTITY_TYPE_UNKNOWN);
+        s2.setSpaceId(space1);
+        s2.setProjectId(project1inSpace1);
+        s2.setContainerId(s1PermId);
+        v3api.createSamples(systemSessionToken, Arrays.asList(s2)).get(0);
+        
+        SampleFetchOptions fetchOptions = new SampleFetchOptions();
+        fetchOptions.withProject();
+        fetchOptions.withContainer();
+        List<ISampleId> ids = new ArrayList<>();
+        ids.add(new SampleIdentifier("/SPACE1/PROJECT1/" + sampleCode));
+        ids.add(new SampleIdentifier("/SPACE1/PROJECT1/" + sampleCode + ":A01"));
+        Map<ISampleId, Sample> samples = v3api.mapSamples(systemSessionToken, ids, fetchOptions);
+        assertEquals(samples.get(ids.get(0)).getProject().getIdentifier().toString(), "/SPACE1/PROJECT1");
+        assertEquals(samples.get(ids.get(1)).getProject().getIdentifier().toString(), "/SPACE1/PROJECT1");
+    }
 
     @Test
     public void testAssignSpaceSampleToAProject()
     {
-        String sampleCode = createUniqueCode();
+        String sampleCode = createUniqueCode("S");
         SamplePermId spaceSample = createSamples(systemSessionToken, space1, null, null, sampleCode).get(0);
         SampleUpdate sampleUpdate = new SampleUpdate();
-        sampleUpdate.setSampleId(spaceSample);
+        sampleUpdate.setSampleId(new SampleIdentifier("/SPACE1/" + sampleCode));
         sampleUpdate.setProjectId(project1inSpace1);
         Date now = new Date();
         
@@ -264,10 +264,10 @@ public class ProjectSampleTest extends BaseTest
     @Test
     public void testAssignProjectSampleToADifferentProjectInTheSameSpace()
     {
-        String sampleCode = createUniqueCode();
+        String sampleCode = createUniqueCode("S");
         SamplePermId spaceSample = createSamples(systemSessionToken, space1, project1inSpace1, null, sampleCode).get(0);
         SampleUpdate sampleUpdate = new SampleUpdate();
-        sampleUpdate.setSampleId(spaceSample);
+        sampleUpdate.setSampleId(new SampleIdentifier("/SPACE1/PROJECT1/" + sampleCode));
         sampleUpdate.setProjectId(project2inSpace1);
         Date now = new Date();
         
@@ -288,7 +288,7 @@ public class ProjectSampleTest extends BaseTest
     @Test
     public void testAssignProjectSampleToAProjectInADifferentSpace()
     {
-        String sampleCode = createUniqueCode();
+        String sampleCode = createUniqueCode("S");
         SamplePermId spaceSample = createSamples(systemSessionToken, space1, project1inSpace1, null, sampleCode).get(0);
         SampleUpdate sampleUpdate = new SampleUpdate();
         sampleUpdate.setSampleId(spaceSample);
@@ -313,7 +313,7 @@ public class ProjectSampleTest extends BaseTest
     @Test
     public void testUnassignProjectSampleFromProject()
     {
-        String sampleCode = createUniqueCode();
+        String sampleCode = createUniqueCode("S");
         SamplePermId spaceSample = createSamples(systemSessionToken, space1, project1inSpace1, null, sampleCode).get(0);
         SampleUpdate sampleUpdate = new SampleUpdate();
         sampleUpdate.setSampleId(spaceSample);
@@ -390,7 +390,8 @@ public class ProjectSampleTest extends BaseTest
         creation.setTypeId(ENTITY_TYPE_UNKNOWN);
         creation.setSpaceId(space1);
         creation.setProjectId(project2inSpace1);
-        creation.setExperimentId(experimentInProject1InSpace1);
+        String expCode = createUniqueCode("E");
+        creation.setExperimentId(createExperimentInProject1OfSpace1(expCode));
         
         assertUserFailureException(new IDelegatedAction()
         {
@@ -402,7 +403,7 @@ public class ProjectSampleTest extends BaseTest
         }, "Sample project must be the same as experiment project. "
                 + "Sample: /SPACE1/SAMPLE_WITH_INCONSISTENT_PROJECT_AND_EXPERIMENT, "
                 + "Project: /SPACE1/PROJECT2, "
-                + "Experiment: /SPACE1/PROJECT1/EXP1 "
+                + "Experiment: /SPACE1/PROJECT1/" + expCode + " "
                 + "(Context: [verify experiment for sample SAMPLE_WITH_INCONSISTENT_PROJECT_AND_EXPERIMENT])");
     }
     
@@ -410,8 +411,10 @@ public class ProjectSampleTest extends BaseTest
     @Transactional(propagation = Propagation.NEVER)
     public void testAssignSpaceSampleToProjectInDifferentSpace()
     {
+        String code = createUniqueCode("S");
+        ISampleId sampleId = createSamples(systemSessionToken, space1, null, null, code).get(0);
         final SampleUpdate sampleUpdate = new SampleUpdate();
-        sampleUpdate.setSampleId(sample1InSpace1);
+        sampleUpdate.setSampleId(sampleId);
         sampleUpdate.setProjectId(project2InSpace2);
 
         assertUserFailureException(new IDelegatedAction()
@@ -422,17 +425,19 @@ public class ProjectSampleTest extends BaseTest
                 v3api.updateSamples(systemSessionToken, Collections.singletonList(sampleUpdate));
             }
         }, "Sample space must be the same as project space. "
-                + "Sample: /SPACE1/SAMPLE1, "
+                + "Sample: /SPACE1/" + code + ", "
                 + "Project: /SPACE2/PROJECT2 "
-                + "(Context: [verify project for sample SAMPLE1])");
+                + "(Context: [verify project for sample " + code + "])");
     }
     
     @Test
     @Transactional(propagation = Propagation.NEVER)
     public void testAssignSampleOfAnExperimentToProjectDifferentToTheExperimentProject()
     {
-        String sampleCode = createUniqueCode();
-        SamplePermId sample = createSamples(systemSessionToken, space1, null, experimentInProject1InSpace1, sampleCode).get(0);
+        String sampleCode = createUniqueCode("S");
+        String expCode = createUniqueCode("E");
+        IExperimentId experiment = createExperimentInProject1OfSpace1(expCode);
+        SamplePermId sample = createSamples(systemSessionToken, space1, null, experiment, sampleCode).get(0);
         final SampleUpdate sampleUpdate = new SampleUpdate();
         sampleUpdate.setSampleId(sample);
         sampleUpdate.setProjectId(project2inSpace1);
@@ -447,7 +452,7 @@ public class ProjectSampleTest extends BaseTest
         }, "Sample project must be the same as experiment project. "
                 + "Sample: /SPACE1/" + sampleCode + ", "
                 + "Project: /SPACE1/PROJECT2, "
-                + "Experiment: /SPACE1/PROJECT1/EXP1 "
+                + "Experiment: /SPACE1/PROJECT1/" + expCode + " "
                 + "(Context: [verify experiment for sample " + sampleCode + "])");
         
     }
@@ -456,8 +461,10 @@ public class ProjectSampleTest extends BaseTest
     @Transactional(propagation = Propagation.NEVER)
     public void testAssignSharedSampleToProject()
     {
+        String code = createUniqueCode("S");
+        ISampleId sharedSample = createSamples(systemSessionToken, null, null, null, code).get(0);
         final SampleUpdate sampleUpdate = new SampleUpdate();
-        sampleUpdate.setSampleId(sharedSample1);
+        sampleUpdate.setSampleId(sharedSample);
         sampleUpdate.setProjectId(project1inSpace1);
         
         assertUserFailureException(new IDelegatedAction()
@@ -468,9 +475,9 @@ public class ProjectSampleTest extends BaseTest
                 v3api.updateSamples(systemSessionToken, Collections.singletonList(sampleUpdate));
             }
         }, "Shared samples cannot be attached to projects. "
-                + "Sample: /SHARED1, "
+                + "Sample: /" + code + ", "
                 + "Project: /SPACE1/PROJECT1 "
-                + "(Context: [verify project for sample SHARED1])");
+                + "(Context: [verify project for sample " + code + "])");
     }
     
     @Test(priority = -1)
@@ -583,10 +590,19 @@ public class ProjectSampleTest extends BaseTest
         assertEquals(renderedReferenceDate.compareTo(renderedActualDate) <= 0, true,
                 renderedActualDate + " > " + renderedReferenceDate);
     }
+    
+    private IExperimentId createExperimentInProject1OfSpace1(String code)
+    {
+        ExperimentCreation experiment = new ExperimentCreation();
+        experiment.setCode(code);
+        experiment.setTypeId(ENTITY_TYPE_UNKNOWN);
+        experiment.setProjectId(project1inSpace1);
+        return v3api.createExperiments(systemSessionToken, Arrays.asList(experiment)).get(0);
+    }
 
-    private String createUniqueCode()
+    private String createUniqueCode(String prefix)
     {
-        return "S-" + System.currentTimeMillis();
+        return prefix + "-" + System.currentTimeMillis();
     }
     
     private void waitAtLeastASecond()
-- 
GitLab