From b21ab898f7f370d7df877f06a6985f1ccea4d3d5 Mon Sep 17 00:00:00 2001
From: izabel <izabel>
Date: Wed, 2 Jun 2010 07:26:26 +0000
Subject: [PATCH] [LMS-1529] create wells on the fly when datasets are
 registered before the library is registered; allow to "update" already
 existing samples on "Library" registration

SVN: 16251
---
 .../ch/systemsx/cisd/bds/hcs/Location.java    |  13 +++
 .../systemsx/cisd/bds/hcs/LocationTest.java   |  21 ++++
 .../cisd/common/geometry/ConversionUtils.java |  15 +++
 .../server/EncapsulatedOpenBISService.java    |   9 ++
 .../shared/IEncapsulatedOpenBISService.java   |   7 ++
 .../openbis/generic/server/ETLService.java    |  93 +++++++++++++++
 .../generic/server/ETLServiceLogger.java      |  12 ++
 .../authorization/PredicateExecutor.java      |   6 +
 .../generic/shared/IETLLIMSService.java       |  16 +++
 .../IAuthorizationDataProvider.java           |   6 +
 .../SampleComponentsDescriptionPredicate.java |  51 ++++++++
 .../predicate/SamplePermIdPredicate.java      |  74 ++++++++++++
 .../openbis/generic/shared/basic/PermId.java  |  94 +++++++++++++++
 .../shared/basic/dto/NewSamplesWithTypes.java |  12 ++
 .../dto/SampleComponentsDescription.java      |  73 ++++++++++++
 .../server/GenericBusinessObjectFactory.java  |   7 ++
 .../plugin/generic/server/GenericServer.java  |  55 ++++++++-
 .../generic/server/GenericServerLogger.java   |  15 +++
 .../server/IGenericBusinessObjectFactory.java |   3 +
 .../server/SampleRegisterOrUpdateUtil.java    | 110 ++++++++++++++++++
 .../plugin/generic/shared/IGenericServer.java |  11 ++
 .../shared/IETLLIMSService.java.expected      |  16 +++
 .../shared/IGenericServer.java.expected       |  11 ++
 .../openbis/dss/etl/HCSDatasetUploader.java   |  61 +++++++---
 .../dss/etl/dataaccess/IImagingUploadDAO.java |   4 +-
 .../dss/etl/dataaccess/ImgSpotDTO.java        |  16 ++-
 .../client/web/server/LibraryExtractor.java   |  12 +-
 .../web/server/LibraryRegistrationTask.java   |   2 +-
 .../shared/basic/dto/ScreeningConstants.java  |   4 +
 .../source/sql/postgresql/001/schema-001.sql  |   2 +
 .../etl/dataaccess/ImagingUploadDAOTest.java  |   3 +
 31 files changed, 814 insertions(+), 20 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SampleComponentsDescriptionPredicate.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SamplePermIdPredicate.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/PermId.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SampleComponentsDescription.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/SampleRegisterOrUpdateUtil.java

diff --git a/bds/source/java/ch/systemsx/cisd/bds/hcs/Location.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/Location.java
index 9641a1729c2..6f7ad4509e0 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/hcs/Location.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/Location.java
@@ -92,6 +92,19 @@ public final class Location
         }
     }
 
+    /**
+     * For given location returns corresponding matrix coordinate.
+     */
+    public static final String tryCreateMatrixCoordinateFromLocation(final Location location)
+    {
+        if (location == null)
+        {
+            throw new IllegalArgumentException("Location unspecified");
+        }
+        Point point = new Point(location.getY() - 1, location.getX() - 1);
+        return ConversionUtils.convertToSpreadsheetLocation(point);
+    }
+
     //
     // Object
     //
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/LocationTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/LocationTest.java
index 7e3cb097282..8b072e1054c 100644
--- a/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/LocationTest.java
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/LocationTest.java
@@ -101,4 +101,25 @@ public final class LocationTest
         assertEquals(new Location(7, 26), Location.tryCreateLocationFromMatrixCoordinate("z7"));
         assertEquals(new Location(34, 15), Location.tryCreateLocationFromMatrixCoordinate("O34"));
     }
+
+    @Test
+    public final void testCreateMatrixCoordinateFromLocation()
+    {
+        assertEquals("A1", Location.tryCreateMatrixCoordinateFromLocation(new Location(1, 1)));
+        assertEquals("A2", Location.tryCreateMatrixCoordinateFromLocation(new Location(2, 1)));
+        assertEquals("Z7", Location.tryCreateMatrixCoordinateFromLocation(new Location(7, 26)));
+        assertEquals("O34", Location.tryCreateMatrixCoordinateFromLocation(new Location(34, 15)));
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public final void testCreateMatrixCoordinateFromTooBigNumber()
+    {
+        Location.tryCreateMatrixCoordinateFromLocation(new Location(134, 27));
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public final void testCreateMatrixCoordinateFromNullLocation()
+    {
+        Location.tryCreateMatrixCoordinateFromLocation(null);
+    }
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/geometry/ConversionUtils.java b/common/source/java/ch/systemsx/cisd/common/geometry/ConversionUtils.java
index 3c0d8e92b43..a07404e2d33 100644
--- a/common/source/java/ch/systemsx/cisd/common/geometry/ConversionUtils.java
+++ b/common/source/java/ch/systemsx/cisd/common/geometry/ConversionUtils.java
@@ -90,6 +90,21 @@ public class ConversionUtils
 
     }
 
+    static public String convertToSpreadsheetLocation(Point p)
+    {
+        if (p == null)
+        {
+            throw new IllegalArgumentException("Unspecified point.");
+        }
+        if (p.getX() >= 26 || p.getX() < 0)
+        {
+            throw new IllegalArgumentException("Unsupported value: " + p.getX());
+        }
+        char x = (char) (p.getX() + 'A');
+        int y = p.getY() + 1;
+        return String.format("%s%s", x, y);
+    }
+
     private static int getLetterNumber(final char ch)
     {
         return Character.toUpperCase(ch) - 'A' + 1;
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
index 50f357308a1..b4df1ab81f4 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
@@ -17,6 +17,8 @@
 package ch.systemsx.cisd.openbis.dss.generic.server;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.apache.log4j.Logger;
 import org.springframework.beans.factory.FactoryBean;
@@ -52,6 +54,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DatastoreServiceDescriptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleComponentsDescription;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SimpleDataSetInformationDTO;
@@ -697,4 +700,10 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
     {
         service.unarchiveDatasets(sessionToken, dataSetCodes);
     }
+
+    public Map<String, String> listOrRegisterComponents(String containerPermId, Set<String> codes,
+            String sampleTypeCode) throws UserFailureException
+    {
+        return service.listOrRegisterComponents(sessionToken, new SampleComponentsDescription(containerPermId, codes, sampleTypeCode));
+    }
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java
index 0d87d03d0c0..aefe20cecb5 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IEncapsulatedOpenBISService.java
@@ -17,6 +17,8 @@
 package ch.systemsx.cisd.openbis.dss.generic.shared;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
@@ -38,6 +40,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleComponentsDescription;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SimpleDataSetInformationDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
@@ -223,4 +226,8 @@ public interface IEncapsulatedOpenBISService
 
     /** See {@link IETLLIMSService#checkSpaceAccess(String, SpaceIdentifier)} */
     public void checkSpaceAccess(String sToken, SpaceIdentifier spaceId);
+
+    /** See {@link IETLLIMSService#listOrRegisterComponents(String, SampleComponentsDescription)} */
+    public Map<String, String> listOrRegisterComponents(final String containerPermId,
+            Set<String> codes, String sampleTypeCode) throws UserFailureException;
 }
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
index e0a4d496a16..4dfbea3e51e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
@@ -18,10 +18,12 @@ package ch.systemsx.cisd.openbis.generic.server;
 
 import static ch.systemsx.cisd.openbis.generic.shared.GenericSharedConstants.DATA_STORE_SERVER_WEB_APPLICATION_NAME;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import ch.systemsx.cisd.authentication.IAuthenticationService;
@@ -84,6 +86,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PropertyTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleComponentsDescription;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePropertyPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePE;
@@ -705,4 +708,94 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
 
         return url;
     }
+
+    public Map<String, String> listOrRegisterComponents(String sessionToken,
+            SampleComponentsDescription components) throws UserFailureException
+    {
+        assert sessionToken != null : "Unspecified session token.";
+        assert components != null : "Unspecified components.";
+
+        final Session session = getSession(sessionToken);
+        SamplePE container = getContainer(components.getContainerPermId());
+        Set<String> codesToGet = new HashSet<String>(components.getCodes());
+        Map<String, String> result = new HashMap<String, String>();
+        tryLoadSamples(session, listSamplesForContainer(session, container), codesToGet, result);
+        if (codesToGet.size() > 0)
+        {
+            registerSamples(components.getSampleTypeCode(), session, container, codesToGet);
+            tryLoadSamples(session, listSamplesForContainer(session, container), codesToGet, result);
+        }
+        assert codesToGet.size() == 0 : "All samples should exist at this point";
+        return result;
+    }
+
+    private void registerSamples(String sampleTypeCode, final Session session, SamplePE container,
+            Set<String> codesToGet)
+    {
+        final SampleTypePE sampleTypePE = getSampleType(sampleTypeCode);
+        List<NewSample> newSamples = new ArrayList<NewSample>();
+        for (String code : codesToGet)
+        {
+            String groupPrefix =
+                    container.getGroup() != null ? ("/" + container.getGroup().getCode() + "/")
+                            : "/";
+            newSamples.add(new NewSample(groupPrefix + code, SampleTypeTranslator.translate(
+                    sampleTypePE, null), null, container.getIdentifier()));
+        }
+        getSampleTypeSlaveServerPlugin(sampleTypePE).registerSamples(session, newSamples);
+    }
+
+    private SamplePE getContainer(String containerPermId)
+    {
+        SamplePE container = daoFactory.getSampleDAO().tryToFindByPermID(containerPermId);
+        if (container == null)
+        {
+            throw new UserFailureException(String.format("Container '%s' not found.",
+                    containerPermId));
+        }
+        return container;
+    }
+
+    List<Sample> listSamplesForContainer(Session session, SamplePE container)
+    {
+        ListOrSearchSampleCriteria criteria = createListSamplesByContainerCriteria(container);
+        return businessObjectFactory.createSampleLister(session).list(criteria);
+    }
+
+    private SampleTypePE getSampleType(String sampleTypeCode)
+    {
+        final SampleTypePE sampleTypePE =
+                getDAOFactory().getSampleTypeDAO().tryFindSampleTypeByCode(sampleTypeCode);
+        if (sampleTypePE == null)
+        {
+            throw UserFailureException.fromTemplate("Sample type with code '%s' does not exist.",
+                    sampleTypeCode);
+        }
+        return sampleTypePE;
+    }
+
+    private static ListOrSearchSampleCriteria createListSamplesByContainerCriteria(
+            SamplePE container)
+    {
+        ListOrSearchSampleCriteria criteria =
+                new ListOrSearchSampleCriteria(ListOrSearchSampleCriteria
+                        .createForContainer(new TechId(container.getId())));
+        criteria.setEnrichDependentSamplesWithProperties(false);
+        return criteria;
+    }
+
+    private static void tryLoadSamples(final Session session, List<Sample> samples,
+            Set<String> codesToGet, Map<String, String> result)
+    {
+        for (Sample s : samples)
+        {
+            String subCode = s.getSubCode();
+            if (codesToGet.contains(subCode))
+            {
+                result.put(subCode, s.getPermId());
+                codesToGet.remove(subCode);
+            }
+        }
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
index 1392463bf4f..35b409bb47c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.server;
 
 import java.util.List;
+import java.util.Map;
 
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
@@ -41,6 +42,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DatastoreServiceDescriptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleComponentsDescription;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
@@ -276,4 +278,14 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
         logAccess(sessionToken, "getDefaultDataStoreBaseURL");
         return null;
     }
+
+    public Map<String, String> listOrRegisterComponents(String sessionToken,
+            SampleComponentsDescription components)
+            throws UserFailureException
+    {
+        logTracking(sessionToken, "listOrRegisterComponents", "CONTAINER(%s) CODES(%s) TYPE(%s)",
+                components.getContainerPermId(), components.getCodes().size(), components.getSampleTypeCode());
+
+        return null;
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java
index 32a166434b6..e14b13d96a0 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java
@@ -34,6 +34,7 @@ import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.ShouldFl
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ArrayPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.CollectionPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.IPredicate;
+import ch.systemsx.cisd.openbis.generic.shared.basic.PermId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetAccessPE;
@@ -331,6 +332,11 @@ public final class PredicateExecutor
             return daoFactory.getSampleDAO().getByTechId(techId);
         }
 
+        public SamplePE getSample(PermId id)
+        {
+            return daoFactory.getSampleDAO().tryToFindByPermID(id.getId());
+        }
+
         public GridCustomFilterPE getGridCustomFilter(TechId techId)
         {
             return daoFactory.getGridCustomFilterDAO().getByTechId(techId);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
index 46d3c841541..32900ffa715 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.shared;
 
 import java.util.List;
+import java.util.Map;
 
 import org.springframework.transaction.annotation.Transactional;
 
@@ -32,6 +33,7 @@ import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSampl
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSamplesByPropertyPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.NewExperimentPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.NewSamplePredicate;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleComponentsDescriptionPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleOwnerIdentifierPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleTechIdPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleUpdatesPredicate;
@@ -58,6 +60,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataStoreServerInfo;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleComponentsDescription;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SimpleDataSetInformationDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
@@ -407,4 +410,17 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleSet.USER)
     public void checkSpaceAccess(String sessionToken,
             @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) SpaceIdentifier spaceId);
+
+    /**
+     * Load perm ids of samples contained in given container. Register samples that don't exist.
+     * 
+     */
+    @Transactional
+    @RolesAllowed(RoleSet.ETL_SERVER)
+    @DatabaseCreateOrDeleteModification(value = ObjectKind.SAMPLE)
+    public Map<String, String> listOrRegisterComponents(
+            final String sessionToken,
+            @AuthorizationGuard(guardClass = SampleComponentsDescriptionPredicate.class) SampleComponentsDescription components)
+            throws UserFailureException;
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java
index c7e3fb51d84..66de4b37e1b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.generic.shared.authorization;
 import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.IDatabaseInstanceFinder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.PermId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetAccessPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.GridCustomColumnPE;
@@ -69,6 +70,11 @@ public interface IAuthorizationDataProvider extends IDatabaseInstanceFinder
      */
     public SamplePE getSample(TechId techId);
 
+    /**
+     * Returns the sample with given <var>permId</var>.
+     */
+    public SamplePE getSample(PermId id);
+
     /**
      * Returns the filter with given <var>techId</var>
      */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SampleComponentsDescriptionPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SampleComponentsDescriptionPredicate.java
new file mode 100644
index 00000000000..1ff04eab3a6
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SampleComponentsDescriptionPredicate.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.authorization.predicate;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.PermId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleComponentsDescription;
+
+/**
+ * An <code>IPredicate</code> implementation for {@link SampleComponentsDescription}.
+ * 
+ * @author Izabela Adamczyk
+ */
+public final class SampleComponentsDescriptionPredicate extends
+        DelegatedPredicate<PermId, SampleComponentsDescription>
+{
+    public SampleComponentsDescriptionPredicate()
+    {
+        super(new SamplePermIdPredicate());
+    }
+
+    //
+    // DelegatedPredicate
+    //
+
+    @Override
+    public final PermId convert(final SampleComponentsDescription value)
+    {
+        return new PermId(value.getContainerPermId());
+    }
+
+    @Override
+    public final String getCandidateDescription()
+    {
+        return "new sample";
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SamplePermIdPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SamplePermIdPredicate.java
new file mode 100644
index 00000000000..4a9a0214a8d
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SamplePermIdPredicate.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2009 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.authorization.predicate;
+
+import java.util.List;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationDataProvider;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.RoleWithIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.basic.PermId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
+
+/**
+ * An <code>IPredicate</code> implementation based on permId of a sample.
+ * 
+ * @author Izabela Adamczyk
+ */
+public class SamplePermIdPredicate extends AbstractDatabaseInstancePredicate<PermId>
+{
+
+    private final SampleOwnerIdentifierPredicate sampleOwnerIdentifierPredicate;
+
+    public SamplePermIdPredicate()
+    {
+        this(true);
+    }
+
+    public SamplePermIdPredicate(boolean isReadAccess)
+    {
+        sampleOwnerIdentifierPredicate = new SampleOwnerIdentifierPredicate(isReadAccess);
+    }
+
+    //
+    // AbstractPredicate
+    //
+
+    @Override
+    public final void init(IAuthorizationDataProvider provider)
+    {
+        super.init(provider);
+        sampleOwnerIdentifierPredicate.init(provider);
+    }
+
+    @Override
+    public final String getCandidateDescription()
+    {
+        return "sample perm id";
+    }
+
+    @Override
+    final Status doEvaluation(final PersonPE person, final List<RoleWithIdentifier> allowedRoles,
+            final PermId id)
+    {
+        SamplePE sample = authorizationDataProvider.getSample(id);
+        return sampleOwnerIdentifierPredicate.doEvaluation(person, allowedRoles, sample
+                .getSampleIdentifier());
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/PermId.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/PermId.java
new file mode 100644
index 00000000000..ee8a21427f4
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/PermId.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2009 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic;
+
+import java.io.Serializable;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
+
+/**
+ * Perm id of an entity.
+ * 
+ * @author Izabela Adamczyk
+ */
+public class PermId implements IsSerializable, Serializable
+{
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    private String id;
+
+    protected PermId()
+    {
+        // for serialization
+    }
+
+    public PermId(String id)
+    {
+        assert id != null : "id cannot be null";
+        this.id = id;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public void setId(String id)
+    {
+        this.id = id;
+    }
+
+    public static PermId create(IPermIdHolder holder)
+    {
+        if (holder == null || holder.getPermId() == null)
+        {
+            return null;
+        } else
+        {
+            return new PermId(holder.getPermId());
+        }
+    }
+
+    @Override
+    public final boolean equals(final Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+        if (obj instanceof PermId == false)
+        {
+            return false;
+        }
+        return this.toString().equals(obj.toString());
+    }
+
+    @Override
+    public final int hashCode()
+    {
+        return this.toString().hashCode();
+    }
+
+    @Override
+    public String toString()
+    {
+        return id;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/NewSamplesWithTypes.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/NewSamplesWithTypes.java
index 4215e1ea27e..3c472c69c5e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/NewSamplesWithTypes.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/NewSamplesWithTypes.java
@@ -29,6 +29,18 @@ public class NewSamplesWithTypes
 
     List<NewSample> newSamples;
 
+    boolean allowUpdateIfExist = false;
+
+    public boolean isAllowUpdateIfExist()
+    {
+        return allowUpdateIfExist;
+    }
+
+    public void setAllowUpdateIfExist(boolean allowUpdateIfExist)
+    {
+        this.allowUpdateIfExist = allowUpdateIfExist;
+    }
+
     public NewSamplesWithTypes()
     {
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SampleComponentsDescription.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SampleComponentsDescription.java
new file mode 100644
index 00000000000..2a62309b960
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SampleComponentsDescription.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.dto;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import ch.systemsx.cisd.openbis.generic.shared.IServer;
+
+/**
+ * Stores basic information about sample components.
+ * 
+ * @author Izabela Adamczyk
+ */
+public class SampleComponentsDescription implements IsSerializable, Serializable
+{
+    private static final long serialVersionUID = IServer.VERSION;
+
+    private final String containerPermId;
+
+    private Set<String> codes;
+
+    private String sampleTypeCode;
+
+    public SampleComponentsDescription(String containerPermId, Set<String> codes,
+            String sampleTypeCode)
+    {
+        this.containerPermId = containerPermId;
+        this.codes = codes;
+        this.sampleTypeCode = sampleTypeCode;
+    }
+
+    public String getContainerPermId()
+    {
+        return containerPermId;
+    }
+
+    public Set<String> getCodes()
+    {
+        return codes;
+    }
+
+    public void setCodes(Set<String> codes)
+    {
+        this.codes = codes;
+    }
+
+    public String getSampleTypeCode()
+    {
+        return sampleTypeCode;
+    }
+
+    public void setSampleTypeCode(String sampleTypeCode)
+    {
+        this.sampleTypeCode = sampleTypeCode;
+    }
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java
index a3c7049fd9c..b80b50c230f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericBusinessObjectFactory.java
@@ -26,6 +26,8 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IProjectBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleTable;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.SampleLister;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.plugin.AbstractPluginBusinessObjectFactory;
 import ch.systemsx.cisd.openbis.plugin.generic.shared.ResourceNames;
@@ -83,4 +85,9 @@ public final class GenericBusinessObjectFactory extends AbstractPluginBusinessOb
     {
         return getCommonBusinessObjectFactory().createProjectBO(session);
     }
+
+    public ISampleLister createSampleLister(Session session)
+    {
+        return SampleLister.create(getDaoFactory(), session.getBaseIndexURL());
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
index dd938ccd1c9..1edcea074df 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Resource;
 
@@ -63,6 +64,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleBatchUpdateDetails;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
@@ -253,6 +255,38 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
                 filename, version));
     }
 
+    public final void registerOrUpdateSamples(final String sessionToken,
+            final List<NewSamplesWithTypes> newSamplesWithType) throws UserFailureException
+    {
+        assert sessionToken != null : "Unspecified session token.";
+        final Session session = getSession(sessionToken);
+        for (NewSamplesWithTypes samples : newSamplesWithType)
+        {
+            if (samples.isAllowUpdateIfExist() == false)
+            {
+                registerSamples(session, samples);
+            } else
+            {
+                registerOrUpdate(session, samples);
+            }
+        }
+    }
+
+    private void registerOrUpdate(final Session session, NewSamplesWithTypes samples)
+    {
+        List<Sample> existingSamples =
+                businessObjectFactory.createSampleLister(session).list(
+                        SampleRegisterOrUpdateUtil.createListSamplesByCodeCriteria(samples
+                                .getNewSamples()));
+        List<NewSample> samplesToUpdate =
+                SampleRegisterOrUpdateUtil.getSamplesToUpdate(samples, existingSamples);
+        List<NewSample> samplesToRegister = new ArrayList<NewSample>(samples.getNewSamples());
+        samplesToRegister.removeAll(samplesToUpdate);
+        registerSamples(session,
+                new NewSamplesWithTypes(samples.getSampleType(), samplesToRegister));
+        updateSamples(session, new NewSamplesWithTypes(samples.getSampleType(), samplesToUpdate));
+    }
+
     public final void registerSamples(final String sessionToken,
             final List<NewSamplesWithTypes> newSamplesWithType) throws UserFailureException
     {
@@ -368,7 +402,7 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
             final String parentIdentifierOrNull = updatedSample.getParentIdentifier();
             final String containerIdentifierOrNull = updatedSample.getContainerIdentifier();
             final SampleBatchUpdateDetails batchUpdateDetails =
-                    ((UpdatedSample) updatedSample).getBatchUpdateDetails();
+                    createBatchUpdateDetails(updatedSample);
 
             batchUpdateSample(session, new SampleBatchUpdatesDTO(oldSampleIdentifier, properties,
                     experimentIdentifierOrNull, newSampleIdentifier, parentIdentifierOrNull,
@@ -376,6 +410,25 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
         }
     }
 
+    SampleBatchUpdateDetails createBatchUpdateDetails(NewSample sample)
+    {
+        if (sample instanceof UpdatedSample)
+        {
+            return ((UpdatedSample) sample).getBatchUpdateDetails();
+        } else
+        {
+            IEntityProperty[] properties = sample.getProperties();
+            Set<String> propertyCodes = new HashSet<String>();
+            for (IEntityProperty p : properties)
+            {
+                propertyCodes.add(p.getPropertyType().getCode());
+            }
+            SampleBatchUpdateDetails result =
+                    new SampleBatchUpdateDetails(false, false, false, propertyCodes);
+            return result;
+        }
+    }
+
     public void registerExperiment(String sessionToken, NewExperiment newExperiment,
             final Collection<NewAttachment> attachments)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerLogger.java
index de1ea77f53b..53df577d8cd 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServerLogger.java
@@ -233,4 +233,19 @@ final class GenericServerLogger extends AbstractServerLogger implements IGeneric
                 materialTypeCode, materials.size());
     }
 
+    public void registerOrUpdateSamples(String sessionToken,
+            List<NewSamplesWithTypes> newSamplesWithType) throws UserFailureException
+    {
+        StringBuilder sb = new StringBuilder();
+        for (NewSamplesWithTypes s : newSamplesWithType)
+        {
+            if (sb.length() > 0)
+            {
+                sb.append(",");
+            }
+            sb.append(s.getSampleType().getCode() + ":" + s.getNewSamples().size());
+        }
+        logTracking(sessionToken, "register_or_update_samples", sb.toString());
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java
index 46d8ca1ec6e..9e7339c8765 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/IGenericBusinessObjectFactory.java
@@ -24,6 +24,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IMaterialTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IProjectBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleTable;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 
 /**
@@ -72,4 +73,6 @@ public interface IGenericBusinessObjectFactory
      * Creates a {@link IExternalDataTable} <i>Business Object</i>.
      */
     public IExternalDataTable createExternalDataTable(final Session session);
+    
+    public ISampleLister createSampleLister(Session session);
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/SampleRegisterOrUpdateUtil.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/SampleRegisterOrUpdateUtil.java
new file mode 100644
index 00000000000..1a6748dd991
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/SampleRegisterOrUpdateUtil.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.plugin.generic.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory;
+
+/**
+ * Utility class for sample update or registration.
+ * 
+ * @author Izabela Adamczyk
+ */
+public class SampleRegisterOrUpdateUtil
+{
+    private static final String INSTANCE_SEPARATOR = "/";
+
+    /**
+     * Returns a list of samples that already exist and should be updated.
+     */
+    static List<NewSample> getSamplesToUpdate(NewSamplesWithTypes samples,
+            List<Sample> existingSamples)
+    {
+        List<NewSample> samplesToUpdate = new ArrayList<NewSample>();
+        for (NewSample ns : samples.getNewSamples())
+        {
+            for (Sample es : existingSamples)
+            {
+                if (isMatching(ns.getIdentifier(), es.getIdentifier()))
+                {
+                    samplesToUpdate.add(ns);
+                }
+            }
+        }
+        return samplesToUpdate;
+    }
+
+    /**
+     * Creates {@link ListOrSearchSampleCriteria} narrowing listing result to samples given codes.
+     */
+    static ListOrSearchSampleCriteria createListSamplesByCodeCriteria(List<NewSample> samples)
+    {
+        List<String> codes = new ArrayList<String>();
+        for (NewSample s : samples)
+        {
+            codes.add(extractCodeForSampleListingCriteria(s));
+        }
+        String[] codesAsArray = codes.toArray(new String[0]);
+        ListOrSearchSampleCriteria criteria = new ListOrSearchSampleCriteria(codesAsArray);
+        return criteria;
+    }
+
+    /**
+     * Checks if given identifiers are matching (db instance is not considered).
+     */
+    private static boolean isMatching(String i1, String i2)
+    {
+        if (i1 != null && i2 != null)
+        {
+            return dropDatabaseInstance(i1).equals(dropDatabaseInstance(i2));
+        } else
+        {
+            return i1 == i2;
+        }
+    }
+
+    private static String dropDatabaseInstance(String id)
+    {
+        assert id != null;
+        assert id.contains(INSTANCE_SEPARATOR);
+        if (id.startsWith(INSTANCE_SEPARATOR))
+        {
+            return id;
+        } else
+        {
+            return id.substring(id.indexOf(INSTANCE_SEPARATOR));
+        }
+
+    }
+
+    private static String extractCodeForSampleListingCriteria(NewSample s)
+    {
+        SampleIdentifier parsedIdentifier = SampleIdentifierFactory.parse(s.getIdentifier());
+        String subcode = parsedIdentifier.getSampleSubCode();
+        String code = parsedIdentifier.getSampleCode();
+        return StringUtils.isBlank(subcode) ? code : subcode;
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java
index 1905503bc5b..8c04589f6a5 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java
@@ -143,6 +143,17 @@ public interface IGenericServer extends IServer
             @AuthorizationGuard(guardClass = NewSamplesWithTypePredicate.class) final List<NewSamplesWithTypes> newSamplesWithType)
             throws UserFailureException;
 
+    /**
+     * Registers or updates samples of different types in batches.
+     */
+    @Transactional
+    @RolesAllowed(RoleSet.USER)
+    @DatabaseCreateOrDeleteModification(value = ObjectKind.SAMPLE)
+    public void registerOrUpdateSamples(
+            final String sessionToken,
+            @AuthorizationGuard(guardClass = NewSamplesWithTypePredicate.class) final List<NewSamplesWithTypes> newSamplesWithType)
+            throws UserFailureException;
+
     /**
      * Updates samples of different types in batches.
      */
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
index 46d3c841541..32900ffa715 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.shared;
 
 import java.util.List;
+import java.util.Map;
 
 import org.springframework.transaction.annotation.Transactional;
 
@@ -32,6 +33,7 @@ import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSampl
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSamplesByPropertyPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.NewExperimentPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.NewSamplePredicate;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleComponentsDescriptionPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleOwnerIdentifierPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleTechIdPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleUpdatesPredicate;
@@ -58,6 +60,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataStoreServerInfo;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleComponentsDescription;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SimpleDataSetInformationDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
@@ -407,4 +410,17 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleSet.USER)
     public void checkSpaceAccess(String sessionToken,
             @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) SpaceIdentifier spaceId);
+
+    /**
+     * Load perm ids of samples contained in given container. Register samples that don't exist.
+     * 
+     */
+    @Transactional
+    @RolesAllowed(RoleSet.ETL_SERVER)
+    @DatabaseCreateOrDeleteModification(value = ObjectKind.SAMPLE)
+    public Map<String, String> listOrRegisterComponents(
+            final String sessionToken,
+            @AuthorizationGuard(guardClass = SampleComponentsDescriptionPredicate.class) SampleComponentsDescription components)
+            throws UserFailureException;
+
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java.expected b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java.expected
index 1905503bc5b..8c04589f6a5 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java.expected
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/shared/IGenericServer.java.expected
@@ -143,6 +143,17 @@ public interface IGenericServer extends IServer
             @AuthorizationGuard(guardClass = NewSamplesWithTypePredicate.class) final List<NewSamplesWithTypes> newSamplesWithType)
             throws UserFailureException;
 
+    /**
+     * Registers or updates samples of different types in batches.
+     */
+    @Transactional
+    @RolesAllowed(RoleSet.USER)
+    @DatabaseCreateOrDeleteModification(value = ObjectKind.SAMPLE)
+    public void registerOrUpdateSamples(
+            final String sessionToken,
+            @AuthorizationGuard(guardClass = NewSamplesWithTypePredicate.class) final List<NewSamplesWithTypes> newSamplesWithType)
+            throws UserFailureException;
+
     /**
      * Updates samples of different types in batches.
      */
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSDatasetUploader.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSDatasetUploader.java
index 6bb1120e837..48063b947ad 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSDatasetUploader.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSDatasetUploader.java
@@ -18,11 +18,13 @@ package ch.systemsx.cisd.openbis.dss.etl;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.Map.Entry;
 
+import ch.systemsx.cisd.bds.hcs.Location;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.dss.etl.HCSImageFileExtractionResult.Channel;
 import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingUploadDAO;
@@ -33,6 +35,9 @@ import ch.systemsx.cisd.openbis.dss.etl.dataaccess.ImgContainerDTO;
 import ch.systemsx.cisd.openbis.dss.etl.dataaccess.ImgDatasetDTO;
 import ch.systemsx.cisd.openbis.dss.etl.dataaccess.ImgImageDTO;
 import ch.systemsx.cisd.openbis.dss.etl.dataaccess.ImgSpotDTO;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
+import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 
 /**
  * @author Tomasz Pylak
@@ -127,17 +132,17 @@ class HCSDatasetUploader
             throw createInvalidNewChannelException(expId, existingChannels, channelName);
         }
         // a channel with a specified name already exists for an experiment, its description
-        // will be updated
+            // will be updated
         if (existingChannel.getWavelength().equals(channelDTO.getWavelength()) == false)
-        {
+            {
             throw UserFailureException.fromTemplate(
-                    "There are already datasets registered for the experiment "
-                            + "which use the same channel name, but with a different wavelength! "
+                                "There are already datasets registered for the experiment "
+                                        + "which use the same channel name, but with a different wavelength! "
                             + "Channel %s, old wavelength %d, new wavelength %d.", channelName,
                     existingChannel.getWavelength(), channelDTO.getWavelength());
-        }
-        channelDTO.setId(existingChannel.getId());
-        dao.updateChannel(channelDTO);
+            }
+            channelDTO.setId(existingChannel.getId());
+            dao.updateChannel(channelDTO);
         return channelDTO;
     }
 
@@ -153,10 +158,10 @@ class HCSDatasetUploader
     }
 
     private ImgChannelDTO createChannel(long expId, HCSImageFileExtractionResult.Channel channel)
-    {
+        {
         ImgChannelDTO channelDTO = makeChannelDTO(channel, expId);
-        long channelId = dao.addChannel(channelDTO);
-        channelDTO.setId(channelId);
+            long channelId = dao.addChannel(channelDTO);
+            channelDTO.setId(channelId);
         return channelDTO;
     }
 
@@ -285,16 +290,17 @@ class HCSDatasetUploader
         List<ImgSpotDTO> oldSpots = dao.listSpots(contId);
         List<ImgSpotDTO> newSpots =
                 createNewSpots(contId, images, oldSpots, info.getContainerRows(), info
-                        .getContainerColumns());
+                        .getContainerColumns(), info.getContainerPermId());
         newSpots.addAll(oldSpots);
         return makeTechIdMatrix(newSpots, info.getContainerRows(), info.getContainerColumns());
     }
 
     private List<ImgSpotDTO> createNewSpots(long contId, List<AcquiredPlateImage> images,
-            List<ImgSpotDTO> existingSpots, int rows, int columns)
+            List<ImgSpotDTO> existingSpots, int rows, int columns, String containerPermId)
     {
         Boolean[][] newSpotMatrix = extractNewSpots(rows, columns, images, existingSpots);
         List<ImgSpotDTO> newSpots = makeSpotDTOs(newSpotMatrix, contId);
+        enrichWithPermIds(newSpots, containerPermId);
         for (ImgSpotDTO spot : newSpots)
         {
             long id = dao.addSpot(spot);
@@ -303,8 +309,35 @@ class HCSDatasetUploader
         return newSpots;
     }
 
-    private static Boolean[][] extractNewSpots(int rows, int columns,
-            List<AcquiredPlateImage> images, List<ImgSpotDTO> existingSpots)
+    private void enrichWithPermIds(List<ImgSpotDTO> newSpots, String containerPermId)
+    {
+        Map<String, String> permIds = getOrCreateWells(newSpots, containerPermId);
+        for (ImgSpotDTO spot : newSpots)
+        {
+            spot.setPermId(permIds.get(createCoordinate(spot)));
+        }
+    }
+
+    private Map<String, String> getOrCreateWells(List<ImgSpotDTO> newSpots, String containerPermId)
+    {
+        IEncapsulatedOpenBISService server = ServiceProvider.getOpenBISService();
+        Set<String> codes = new HashSet<String>();
+        for (ImgSpotDTO spot : newSpots)
+        {
+            codes.add(createCoordinate(spot));
+        }
+        return server.listOrRegisterComponents(containerPermId, codes,
+                ScreeningConstants.OLIGO_WELL_TYPE_CODE);
+    }
+
+    String createCoordinate(ImgSpotDTO spot)
+    {
+        return Location.tryCreateMatrixCoordinateFromLocation(new Location(spot.getColumn(), spot
+                .getRow()));
+    }
+
+    private static Boolean[][] extractNewSpots(int rows, int columns, List<AcquiredPlateImage> images,
+            List<ImgSpotDTO> existingSpots)
     {
         Boolean[][] spots = extractExistingSpots(rows, columns, images);
         unmarkSpots(existingSpots, spots);
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingUploadDAO.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingUploadDAO.java
index 54ece75b656..430795bf0d6 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingUploadDAO.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingUploadDAO.java
@@ -103,8 +103,8 @@ public interface IImagingUploadDAO extends TransactionQuery
             + "(?{1.filePath}, ?{1.page}, ?{1.colorComponentAsString}) returning ID")
     public long addImage(ImgImageDTO image);
 
-    @Select("insert into SPOTS (X, Y, CONT_ID) values "
-            + "(?{1.column}, ?{1.row}, ?{1.containerId}) returning ID")
+    @Select("insert into SPOTS (X, Y, CONT_ID, PERM_ID) values "
+            + "(?{1.column}, ?{1.row}, ?{1.containerId}, ?{1.permId}) returning ID")
     public long addSpot(ImgSpotDTO spot);
 
     // updates
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImgSpotDTO.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImgSpotDTO.java
index 8815546b8ab..2927d99d3ca 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImgSpotDTO.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImgSpotDTO.java
@@ -29,6 +29,9 @@ public class ImgSpotDTO extends AbstractHashable
     @AutoGeneratedKeys
     private long id;
 
+    @ResultColumn("PERM_ID")
+    private String permId;
+
     // position in the container, one-based (e.g. well column)
     @ResultColumn("X")
     private Integer column;
@@ -46,13 +49,24 @@ public class ImgSpotDTO extends AbstractHashable
         // All Data-Object classes must have a default constructor.
     }
 
-    public ImgSpotDTO(Integer row, Integer column, long containerId)
+    public ImgSpotDTO(/* String permId, */Integer row, Integer column, long containerId)
     {
+        /* this.permId = permId; */
         this.column = column;
         this.row = row;
         this.containerId = containerId;
     }
 
+    public String getPermId()
+    {
+        return permId;
+    }
+
+    public void setPermId(String permId)
+    {
+        this.permId = permId;
+    }
+
     public long getId()
     {
         return id;
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryExtractor.java
index 1904fc6cc4f..bed76500635 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryExtractor.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryExtractor.java
@@ -108,7 +108,17 @@ class LibraryExtractor
                         .asList(new NamedInputStream(new FileInputStream(platesFile), platesFile
                                 .getName(), null)), null, null, true,
                         BatchOperationKind.REGISTRATION);
-        return prepared.getSamples();
+        List<NewSamplesWithTypes> samples = prepared.getSamples();
+        setUpdatableTypes(samples);
+        return samples;
+    }
+
+    private static void setUpdatableTypes(List<NewSamplesWithTypes> samples)
+    {
+        for (NewSamplesWithTypes s : samples)
+        {
+            s.setAllowUpdateIfExist(true);
+        }
     }
 
     private static List<NewMaterial> extractMaterials(File genesFile) throws FileNotFoundException
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryRegistrationTask.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryRegistrationTask.java
index 98492ed33d6..fe67c6484c5 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryRegistrationTask.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryRegistrationTask.java
@@ -90,7 +90,7 @@ class LibraryRegistrationTask implements Runnable
         }
         try
         {
-            genericServer.registerSamples(sessionToken, newSamplesWithType);
+            genericServer.registerOrUpdateSamples(sessionToken, newSamplesWithType);
             for (NewSamplesWithTypes s : newSamplesWithType)
             {
                 message.append("Successfuly registered " + s.getNewSamples().size()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java
index 40d8d451d4f..66969a8398f 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java
@@ -50,6 +50,10 @@ public class ScreeningConstants
     // type of the dataset which stores image analysis data, there should be at most one
     public static final String IMAGE_ANALYSIS_DATASET_TYPE = "HCS_IMAGE_ANALYSIS_DATA";
 
+    public static final String OLIGO_WELL_TYPE_CODE = "OLIGO_WELL";
+
+    public static final String CONTROL_WELL_TYPE_CODE = "CONTROL_WELL";
+
     public static final String PLATE_PLUGIN_TYPE_CODE = "PLATE";
 
     public static final String LIBRARY_PLUGIN_TYPE_CODE = "LIBRARY";
diff --git a/screening/source/sql/postgresql/001/schema-001.sql b/screening/source/sql/postgresql/001/schema-001.sql
index 5385d12e09b..ddd0cc8dd89 100644
--- a/screening/source/sql/postgresql/001/schema-001.sql
+++ b/screening/source/sql/postgresql/001/schema-001.sql
@@ -45,6 +45,7 @@ CREATE INDEX CONTAINERS_EXPE_IDX ON CONTAINERS(EXPE_ID);
 
 CREATE TABLE SPOTS (
   ID BIGSERIAL NOT NULL,
+  PERM_ID CODE NOT NULL,
 	
 	-- position in the container, one-based
   X INTEGER, 
@@ -52,6 +53,7 @@ CREATE TABLE SPOTS (
   CONT_ID TECH_ID NOT NULL,
   
   PRIMARY KEY (ID),
+  UNIQUE (PERM_ID), 
   CONSTRAINT FK_SPOT_1 FOREIGN KEY (CONT_ID) REFERENCES CONTAINERS (ID) ON DELETE CASCADE ON UPDATE CASCADE
 );
 
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingUploadDAOTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingUploadDAOTest.java
index 9f7992d56c9..a8eb51dcc7f 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingUploadDAOTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingUploadDAOTest.java
@@ -39,6 +39,8 @@ import ch.systemsx.cisd.openbis.dss.etl.dataaccess.ImgImageDTO.ColorComponent;
 public class ImagingUploadDAOTest extends AbstractDBTest
 {
 
+    private static final String PERM_ID = "PERM_ID";
+
     private static final int PAGE = 1;
 
     private static final int Y_TILE_ROW = 2;
@@ -202,6 +204,7 @@ public class ImagingUploadDAOTest extends AbstractDBTest
     private long addSpot(long containerId)
     {
         final ImgSpotDTO spot = new ImgSpotDTO(Y_WELL_ROW, X_WELL_COLUMN, containerId);
+        spot.setPermId(PERM_ID);
         return dao.addSpot(spot);
     }
 
-- 
GitLab