From 5762d37e00652cbe507f88cf533d6dc38e96c3f1 Mon Sep 17 00:00:00 2001
From: kaloyane <kaloyane>
Date: Fri, 14 Jan 2011 13:01:31 +0000
Subject: [PATCH] [LMS-1929] added support for genes with multiple symbols

SVN: 19427
---
 .../cisd/etlserver/PlateDimensionParser.java  |  23 +-
 .../server/business/bo/AttachmentBO.java      |   2 +-
 .../server/util/KeyExtractorFactory.java      |  12 +-
 .../generic/shared/util/EntityHelper.java     |  40 +++
 .../web/server/LibraryRegistrationTask.java   | 133 ----------
 .../web/server/ScreeningClientService.java    |  20 +-
 .../server/LibraryRegistrationTask.java       | 245 ++++++++++++++++++
 .../screening/server/ScreeningServer.java     |  31 ++-
 .../server/ScreeningServerLogger.java         |  13 +-
 .../screening/shared/IScreeningServer.java    |  11 +
 .../shared/dto/PlateDimensionParser.java      |  23 +-
 .../server/LibraryRegistrationTaskTest.java   | 184 +++++++++++++
 12 files changed, 540 insertions(+), 197 deletions(-)
 delete mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryRegistrationTask.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTask.java
 create mode 100644 screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTaskTest.java

diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/PlateDimensionParser.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/PlateDimensionParser.java
index 9a02274cd00..a59141c3afc 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/PlateDimensionParser.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/PlateDimensionParser.java
@@ -17,7 +17,7 @@
 package ch.systemsx.cisd.etlserver;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
+import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
 
 /**
  * Extractor and parser of the plate geometry from an array of properties.
@@ -56,12 +56,13 @@ public class PlateDimensionParser
     public static PlateDimension tryToGetPlateDimension(final IEntityProperty[] properties)
     {
         assert properties != null : "Unspecified properties";
-        final String plateGeometryString =
-                tryFindProperty(properties, PLATE_GEOMETRY_PROPERTY_NAME);
-        if (plateGeometryString == null)
+        IEntityProperty plateGeometryProperty =
+                EntityHelper.tryFindProperty(properties, PLATE_GEOMETRY_PROPERTY_NAME);
+        if (plateGeometryProperty == null)
         {
             return null;
         }
+        final String plateGeometryString = plateGeometryProperty.tryGetAsString();
         final PlateDimension dimension = tryParsePlateDimension(plateGeometryString);
         if (dimension == null)
         {
@@ -103,18 +104,4 @@ public class PlateDimensionParser
         }
     }
 
-    private static String tryFindProperty(final IEntityProperty[] properties,
-            final String propertyCode)
-    {
-        for (final IEntityProperty property : properties)
-        {
-            final PropertyType propertyType = property.getPropertyType();
-            if (propertyType.getCode().equals(propertyCode))
-            {
-                return property.tryGetAsString();
-            }
-        }
-        return null;
-    }
-
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AttachmentBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AttachmentBO.java
index 3ce8b79bc79..389e90215b0 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AttachmentBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AttachmentBO.java
@@ -26,10 +26,10 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentHolderPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EventPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.EventPE.EntityType;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EventType;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
-import ch.systemsx.cisd.openbis.generic.shared.dto.EventPE.EntityType;
 
 /**
  * The only productive implementation of {@link IAttachmentBO}.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/util/KeyExtractorFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/util/KeyExtractorFactory.java
index 1123f8fe99e..a85afcc37f8 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/util/KeyExtractorFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/util/KeyExtractorFactory.java
@@ -17,12 +17,12 @@
 package ch.systemsx.cisd.openbis.generic.server.util;
 
 import ch.systemsx.cisd.common.collections.IKeyExtractor;
+import ch.systemsx.cisd.openbis.generic.shared.basic.ICodeHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IIdHolder;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AuthorizationGroupPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.IIdAndCodeHolder;
 import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PropertyTypePE;
@@ -78,10 +78,9 @@ public final class KeyExtractorFactory
     }
 
     /**
-     * Creates an <code>IKeyExtractor</code> implementation based on {@link IIdAndCodeHolder}
-     * extension.
+     * Creates an <code>IKeyExtractor</code> implementation based on {@link ICodeHolder} extension.
      */
-    public final static <T extends IIdAndCodeHolder> IKeyExtractor<String, T> createCodeKeyExtractor()
+    public final static <T extends ICodeHolder> IKeyExtractor<String, T> createCodeKeyExtractor()
     {
         return new CodeKeyExtractor<T>();
     }
@@ -187,15 +186,14 @@ public final class KeyExtractorFactory
         }
     }
 
-    private final static class CodeKeyExtractor<T extends IIdAndCodeHolder> implements
+    private final static class CodeKeyExtractor<T extends ICodeHolder> implements
             IKeyExtractor<String, T>
     {
-
         //
         // IKeyExtractor
         //
 
-        public final String getKey(final IIdAndCodeHolder id)
+        public final String getKey(final ICodeHolder id)
         {
             return id.getCode();
         }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java
index 19b70f9f9c3..4fc488fb638 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/EntityHelper.java
@@ -20,7 +20,9 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 
 /**
@@ -53,4 +55,42 @@ public class EntityHelper
         type.setCode(code);
         return type;
     }
+
+    /**
+     * @return finds and returns an {@link IEntityProperty} for a specified code. Returns
+     *         <code>null</code> if no matching property is found.
+     */
+    public static IEntityProperty tryFindProperty(Iterable<IEntityProperty> properties,
+            final String propertyCode)
+    {
+        for (final IEntityProperty property : properties)
+        {
+            final PropertyType propertyType = property.getPropertyType();
+            if (propertyType.getCode().equalsIgnoreCase(propertyCode))
+            {
+                return property;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * does the same as {@link #tryFindProperty(Iterable, String)} but with arrays.
+     */
+    // TODO 2011-01-13 KE : could we scratch the usage of arrays and only have lists ?
+    // if so, we could delete this method
+    public static IEntityProperty tryFindProperty(IEntityProperty[] properties,
+            final String propertyCode)
+    {
+        for (final IEntityProperty property : properties)
+        {
+            final PropertyType propertyType = property.getPropertyType();
+            // TODO KE : ugly, why is the extracting logic not uppercasing ??
+            if (propertyType.getCode().equalsIgnoreCase(propertyCode))
+            {
+                return property;
+            }
+        }
+        return null;
+    }
 }
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
deleted file mode 100644
index cdad5cd61ac..00000000000
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/LibraryRegistrationTask.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package ch.systemsx.cisd.openbis.plugin.screening.client.web.server;
-
-import java.util.Date;
-import java.util.List;
-
-import ch.systemsx.cisd.common.mail.IMailClient;
-import ch.systemsx.cisd.common.mail.MailClient;
-import ch.systemsx.cisd.common.mail.MailClientParameters;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
-import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
-
-/**
- * Saves genes, oligos and plates. Sends an email to specified address upon error or completion.
- * 
- * @author Izabela Adamczyk
- */
-class LibraryRegistrationTask implements Runnable
-{
-
-    private static final String SUCCESSFUL_LIBRARY_REGISTARION_STATUS =
-            "Library successfuly registered";
-
-    private static final String UNSUCCESSFUL_LIBRARY_REGISTARION_STATUS =
-            "Library registration failed";
-
-    private final MailClientParameters mailClientParameters;
-
-    private final String sessionToken;
-
-    private final String email;
-
-    private final List<NewMaterial> newGenesOrNull;
-
-    private final List<NewMaterial> newOligosOrNull;
-
-    private final List<NewSamplesWithTypes> newSamplesWithType;
-
-    private final IGenericServer genericServer;
-
-    public LibraryRegistrationTask(final String sessionToken, final String email,
-            final List<NewMaterial> newGenesOrNull, final List<NewMaterial> newOligosOrNull,
-            final List<NewSamplesWithTypes> newSamplesWithType, IGenericServer server,
-            MailClientParameters mailClientParameters)
-    {
-        this.sessionToken = sessionToken;
-        this.email = email;
-        this.newGenesOrNull = newGenesOrNull;
-        this.newOligosOrNull = newOligosOrNull;
-        this.newSamplesWithType = newSamplesWithType;
-        this.genericServer = server;
-        this.mailClientParameters = mailClientParameters;
-    }
-
-    public void run()
-    {
-        Date startDate = new Date();
-        StringBuilder message = new StringBuilder();
-        try
-        {
-            if (newGenesOrNull != null)
-            {
-                genericServer.registerOrUpdateMaterials(sessionToken,
-                        ScreeningConstants.GENE_PLUGIN_TYPE_CODE, newGenesOrNull);
-                message.append("Successfuly saved properties of " + newGenesOrNull.size()
-                        + " genes.\n");
-            }
-        } catch (Exception ex)
-        {
-            message.append("ERROR: Genes could not be saved!\n");
-            message.append(ex.getMessage());
-            sendErrorEmail(message, startDate, email);
-            return;
-        }
-        try
-        {
-            if (newOligosOrNull != null)
-            {
-                genericServer.registerOrUpdateMaterials(sessionToken,
-                        ScreeningConstants.SIRNA_PLUGIN_TYPE_NAME, newOligosOrNull);
-                message.append("Successfuly saved " + newOligosOrNull.size() + " siRNAs.\n");
-            }
-        } catch (Exception ex)
-        {
-            message.append("ERROR: siRNAs could not be saved!\n");
-            message.append(ex.getMessage());
-            sendErrorEmail(message, startDate, email);
-            return;
-        }
-        try
-        {
-            genericServer.registerOrUpdateSamples(sessionToken, newSamplesWithType);
-            for (NewSamplesWithTypes s : newSamplesWithType)
-            {
-                message.append("Successfuly saved " + s.getNewSamples().size()
-                        + " samples of type " + s.getSampleType() + ".\n");
-            }
-        } catch (Exception ex)
-        {
-            message.append("ERROR: Plates and wells could not be saved!\n");
-            message.append(ex.getMessage());
-            sendErrorEmail(message, startDate, email);
-            return;
-        }
-        sendSuccessEmail(message, startDate, email);
-
-    }
-
-    private void sendErrorEmail(StringBuilder content, Date startDate, String recipient)
-    {
-        String subject = addDate(UNSUCCESSFUL_LIBRARY_REGISTARION_STATUS, startDate);
-        sendEmail(subject, content.toString(), recipient);
-    }
-
-    private void sendSuccessEmail(StringBuilder content, Date startDate, String recipient)
-    {
-        String subject = addDate(SUCCESSFUL_LIBRARY_REGISTARION_STATUS, startDate);
-        sendEmail(subject, content.toString(), recipient);
-    }
-
-    private static String addDate(String subject, Date startDate)
-    {
-        return subject + " (initiated at " + startDate + ")";
-    }
-
-    private void sendEmail(String subject, String content, String recipient)
-    {
-        final IMailClient mailClient = new MailClient(mailClientParameters);
-        mailClient.sendMessage(subject, content, null, null, recipient);
-    }
-
-}
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
index be0392b8664..4d4497a3b27 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
@@ -17,9 +17,6 @@
 package ch.systemsx.cisd.openbis.plugin.screening.client.web.server;
 
 import java.util.List;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpSession;
@@ -27,7 +24,6 @@ import javax.servlet.http.HttpSession;
 import org.springframework.stereotype.Component;
 
 import ch.rinn.restrictions.Private;
-import ch.systemsx.cisd.common.mail.MailClientParameters;
 import ch.systemsx.cisd.common.servlet.IRequestContextProvider;
 import ch.systemsx.cisd.common.spring.IUncheckedMultipartFile;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
@@ -50,7 +46,6 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
-import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
 import ch.systemsx.cisd.openbis.plugin.screening.BuildAndEnvironmentInfo;
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.IScreeningClientService;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.IScreeningServer;
@@ -81,15 +76,6 @@ public final class ScreeningClientService extends AbstractClientService implemen
     @Resource(name = ResourceNames.SCREENING_PLUGIN_SERVER)
     private IScreeningServer server;
 
-    @Resource(name = ch.systemsx.cisd.openbis.plugin.generic.shared.ResourceNames.GENERIC_PLUGIN_SERVER)
-    private IGenericServer genericServer;
-
-    @Resource(name = ResourceNames.MAIL_CLIENT_PARAMETERS)
-    private MailClientParameters mailClientParameters;
-
-    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 10, 360,
-            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
-
     public ScreeningClientService()
     {
     }
@@ -221,9 +207,9 @@ public final class ScreeningClientService extends AbstractClientService implemen
                         new LibraryExtractor(file.getInputStream(), details.getSeparator(),
                                 experiment, space, details.getPlateGeometry(), details.getScope());
                 extractor.extract();
-                executor.submit(new LibraryRegistrationTask(sessionToken, details.getUserEmail(),
-                        extractor.getNewGenes(), extractor.getNewOligos(), extractor
-                                .getNewSamplesWithType(), genericServer, mailClientParameters));
+                server.registerLibrary(sessionToken, details.getUserEmail(),
+                        extractor.getNewGenes(), extractor.getNewOligos(),
+                        extractor.getNewSamplesWithType());
             }
         } catch (final ch.systemsx.cisd.common.exceptions.UserFailureException e)
         {
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTask.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTask.java
new file mode 100644
index 00000000000..40944dd1ff8
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTask.java
@@ -0,0 +1,245 @@
+package ch.systemsx.cisd.openbis.plugin.screening.server;
+
+import java.util.Date;
+import java.util.List;
+
+import ch.systemsx.cisd.common.collections.TableMap;
+import ch.systemsx.cisd.common.mail.IMailClient;
+import ch.systemsx.cisd.common.shared.basic.utils.StringUtils;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.server.util.KeyExtractorFactory;
+import ch.systemsx.cisd.openbis.generic.shared.ICommonServer;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListMaterialCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
+import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.translator.MaterialTypeTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
+import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
+
+/**
+ * Saves genes, oligos and plates. Sends an email to specified address upon error or completion.
+ * 
+ * @author Izabela Adamczyk
+ */
+class LibraryRegistrationTask implements Runnable
+{
+
+    private static final String SUCCESSFUL_LIBRARY_REGISTARION_STATUS =
+            "Library successfully registered";
+
+    private static final String UNSUCCESSFUL_LIBRARY_REGISTARION_STATUS =
+            "Library registration failed";
+
+
+    private final String sessionToken;
+
+    private final String email;
+
+    private final List<NewMaterial> newGenesOrNull;
+
+    private final List<NewMaterial> newOligosOrNull;
+
+    private final List<NewSamplesWithTypes> newSamplesWithType;
+
+    private final IGenericServer genericServer;
+
+    private final ICommonServer commonServer;
+
+    private final IDAOFactory daoFactory;
+
+    private final IMailClient mailClient;
+
+
+    public LibraryRegistrationTask(String sessionToken, String email,
+            List<NewMaterial> newGenesOrNull, List<NewMaterial> newOligosOrNull,
+            List<NewSamplesWithTypes> newSamplesWithType, ICommonServer commonServer,
+            IGenericServer server, IDAOFactory daoFactory, IMailClient mailClient)
+    {
+        this.sessionToken = sessionToken;
+        this.email = email;
+        this.newGenesOrNull = newGenesOrNull;
+        this.newOligosOrNull = newOligosOrNull;
+        this.newSamplesWithType = newSamplesWithType;
+        this.commonServer = commonServer;
+        this.genericServer = server;
+        this.daoFactory = daoFactory;
+        this.mailClient = mailClient;
+    }
+
+    public void run()
+    {
+        boolean success = true;
+        Date startDate = new Date();
+        StringBuilder message = new StringBuilder();
+
+        try
+        {
+            // when one of these methods fails it will throw an unchecked exception
+            registerOrUpdateGenes(message);
+            registerOrUpdateOligos(message);
+            registerOrUpdateSamples(message);
+        } catch (RuntimeException rex)
+        {
+            success = false;
+        }
+
+        sendEmail(message.toString(), startDate, email, success);
+    }
+
+    private void registerOrUpdateSamples(StringBuilder message)
+    {
+        try
+        {
+            if (newSamplesWithType != null)
+            {
+                genericServer.registerOrUpdateSamples(sessionToken, newSamplesWithType);
+                for (NewSamplesWithTypes s : newSamplesWithType)
+                {
+                    message.append("Successfuly saved " + s.getNewSamples().size()
+                            + " samples of type " + s.getSampleType() + ".\n");
+                }
+            }
+        } catch (RuntimeException ex)
+        {
+            message.append("ERROR: Plates and wells could not be saved!\n");
+            message.append(ex.getMessage());
+            throw ex;
+        }
+    }
+
+    private void registerOrUpdateOligos(StringBuilder message)
+    {
+        try
+        {
+            if (newOligosOrNull != null)
+            {
+                genericServer.registerOrUpdateMaterials(sessionToken,
+                        ScreeningConstants.SIRNA_PLUGIN_TYPE_NAME, newOligosOrNull);
+                message.append("Successfuly saved " + newOligosOrNull.size() + " siRNAs.\n");
+            }
+        } catch (RuntimeException ex)
+        {
+            message.append("ERROR: siRNAs could not be saved!\n");
+            message.append(ex.getMessage());
+            throw ex;
+        }
+    }
+
+    private void registerOrUpdateGenes(StringBuilder message)
+    {
+        try
+        {
+            if (newGenesOrNull != null)
+            {
+                TableMap<String, Material> existingGenes = listExistingGenes();
+                for (NewMaterial newGene : newGenesOrNull)
+                {
+                    Material existingGene = existingGenes.tryGet(newGene.getCode());
+                    if (existingGene != null)
+                    {
+                        mergeGeneTypeCode(existingGene, newGene);
+                    }
+                }
+                
+                genericServer.registerOrUpdateMaterials(sessionToken,
+                        ScreeningConstants.GENE_PLUGIN_TYPE_CODE, newGenesOrNull);
+                message.append("Successfuly saved properties of " + newGenesOrNull.size()
+                        + " genes.\n");
+            }
+        } catch (RuntimeException ex)
+        {
+            message.append("ERROR: Genes could not be saved!\n");
+            message.append(ex.getMessage());
+            throw ex;
+        }
+    }
+
+    private TableMap<String, Material> listExistingGenes()
+    {
+        EntityTypePE entityTypePE =
+                daoFactory.getEntityTypeDAO(EntityKind.MATERIAL).tryToFindEntityTypeByCode(
+                        ScreeningConstants.GENE_PLUGIN_TYPE_CODE);
+        MaterialType materialType = MaterialTypeTranslator.translateSimple(entityTypePE);
+        List<Material> materials =
+                commonServer.listMaterials(sessionToken, new ListMaterialCriteria(materialType),
+                        true);
+
+        return new TableMap<String, Material>(materials,
+                KeyExtractorFactory.<Material> createCodeKeyExtractor());
+    }
+
+    /**
+     * when an existing gene is being updated, we merge the existing gene symbols with the newly
+     * specified into a space-separated new field. If the new gene does not contain new information
+     * i.e. its gene symbol is already present in the DB, the existing gene symbols are not altered.
+     * <p>
+     * For further information see LMS-1929.
+     */
+    private void mergeGeneTypeCode(Material existingMaterial, NewMaterial newMaterial)
+    {
+        IEntityProperty existingGeneProp =
+                EntityHelper.tryFindProperty(existingMaterial.getProperties(),
+                        ScreeningConstants.GENE_SYMBOLS);
+        if (existingGeneProp == null)
+        {
+            return;
+        }
+
+        String existingGene = existingGeneProp.getValue();
+
+        IEntityProperty newGeneProp =
+                EntityHelper.tryFindProperty(newMaterial.getProperties(),
+                        ScreeningConstants.GENE_SYMBOLS);
+
+        if (newGeneProp == null) 
+        {
+            return;
+        }
+        String mergedGeneType = mergeGeneTypes(existingGene, newGeneProp.getValue());
+        newGeneProp.setValue(mergedGeneType);
+    }
+
+    private String mergeGeneTypes(String existingType, String newType)
+    {
+        if (StringUtils.isBlank(newType) || newType.equals(existingType))
+        {
+            return existingType;
+        }
+
+        boolean ignoreNewType =
+                existingType.startsWith(newType + " ") || existingType.endsWith(" " + newType)
+                        || (existingType.indexOf(" " + newType + " ") > 0);
+
+        if (ignoreNewType)
+        {
+            return existingType;
+        } else
+        {
+            return existingType + " " + newType;
+        }
+    }
+
+
+    private void sendEmail(String content, Date startDate, String recipient,
+            boolean successful)
+    {
+        String status =
+                successful ? SUCCESSFUL_LIBRARY_REGISTARION_STATUS
+                        : UNSUCCESSFUL_LIBRARY_REGISTARION_STATUS;
+
+        String subject = addDate(status, startDate);
+        mailClient.sendMessage(subject, content, null, null, recipient);
+    }
+
+    private static String addDate(String subject, Date startDate)
+    {
+        return subject + " (initiated at " + startDate + ")";
+    }
+
+}
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
index 415964c98ef..00fb39aa222 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
@@ -20,6 +20,9 @@ import java.sql.Connection;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Resource;
 
@@ -31,6 +34,9 @@ import org.springframework.stereotype.Component;
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.mail.IMailClient;
+import ch.systemsx.cisd.common.mail.MailClient;
+import ch.systemsx.cisd.common.mail.MailClientParameters;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.server.AbstractServer;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
@@ -46,6 +52,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListMaterialCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
@@ -54,6 +62,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.VocabularyPE;
 import ch.systemsx.cisd.openbis.generic.shared.translator.SampleTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.translator.VocabularyTranslator;
+import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
 import ch.systemsx.cisd.openbis.plugin.screening.server.dataaccess.IScreeningQuery;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.PlateContentLoader;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.ScreeningApiImpl;
@@ -94,11 +103,20 @@ public final class ScreeningServer extends AbstractServer<IScreeningServer> impl
      */
     public static final int MINOR_VERSION = 4;
 
+    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 10, 360,
+            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+
     @Resource(name = ResourceNames.SCREENING_BUSINESS_OBJECT_FACTORY)
     private IScreeningBusinessObjectFactory businessObjectFactory;
 
     @Resource(name = ch.systemsx.cisd.openbis.generic.shared.ResourceNames.COMMON_SERVER)
-    protected ICommonServer commonServer;
+    private ICommonServer commonServer;
+
+    @Resource(name = ch.systemsx.cisd.openbis.plugin.generic.shared.ResourceNames.GENERIC_PLUGIN_SERVER)
+    private IGenericServer genericServer;
+
+    @Resource(name = ResourceNames.MAIL_CLIENT_PARAMETERS)
+    private MailClientParameters mailClientParameters;
 
     public ScreeningServer()
     {
@@ -198,6 +216,16 @@ public final class ScreeningServer extends AbstractServer<IScreeningServer> impl
         return VocabularyTranslator.translate(vocabulary);
     }
 
+    public void registerLibrary(String sessionToken, String userEmail,
+            List<NewMaterial> newGenesOrNull, List<NewMaterial> newOligosOrNull,
+            List<NewSamplesWithTypes> newSamplesWithType)
+    {
+        IMailClient mailClient = new MailClient(mailClientParameters);
+        executor.submit(new LibraryRegistrationTask(sessionToken, userEmail, newGenesOrNull,
+                newOligosOrNull, newSamplesWithType, commonServer, genericServer, getDAOFactory(),
+                mailClient));
+    }
+
     // --------- IScreeningOpenbisServer - method signature should be changed with care
 
     public List<FeatureVectorDatasetReference> listFeatureVectorDatasets(String sessionToken,
@@ -317,4 +345,5 @@ public final class ScreeningServer extends AbstractServer<IScreeningServer> impl
     {
         return MINOR_VERSION;
     }
+
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java
index 63749e23f33..eb38f03b670 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java
@@ -29,7 +29,9 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
+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.SampleParentWithDerived;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
@@ -244,8 +246,6 @@ final class ScreeningServerLogger extends AbstractServerLogger implements IScree
     public List<Material> listExperimentMaterials(String sessionToken, TechId experimentId,
             MaterialType materialType)
     {
-        logAccess(sessionToken, "listExperimentMaterials", "experimentId(%s), materialType(%s)",
-                experimentId, materialType);
         return null;
     }
 
@@ -258,4 +258,13 @@ final class ScreeningServerLogger extends AbstractServerLogger implements IScree
     {
         return ScreeningServer.MINOR_VERSION;
     }
+
+    public void registerLibrary(String sessionToken, String userEmail,
+            List<NewMaterial> newGenesOrNull, List<NewMaterial> newOligosOrNull,
+            List<NewSamplesWithTypes> newSamplesWithType)
+    {
+        logAccess(sessionToken, "registerLibrary",
+                "userEmail(%s), newGenesOrNull(%s), newOligosOrNull(%s), newSamplesWithType(%s)",
+                userEmail, newGenesOrNull, newOligosOrNull, newSamplesWithType);
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java
index a0eb95d6dd8..d0d16455309 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java
@@ -33,6 +33,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived;
@@ -140,4 +142,13 @@ public interface IScreeningServer extends IServer
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
     public Vocabulary getVocabulary(String sessionToken, String code) throws UserFailureException;
+
+    /**
+     * registers the contents of an uploaded library.
+     */
+    @Transactional
+    @RolesAllowed(RoleWithHierarchy.SPACE_ADMIN)
+    public void registerLibrary(String sessionToken, String userEmail,
+            List<NewMaterial> newGenesOrNull, List<NewMaterial> newOligosOrNull,
+            List<NewSamplesWithTypes> newSamplesWithType);
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/PlateDimensionParser.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/PlateDimensionParser.java
index 48a2171d205..39c9343c846 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/PlateDimensionParser.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/PlateDimensionParser.java
@@ -19,7 +19,7 @@ package ch.systemsx.cisd.openbis.plugin.screening.shared.dto;
 import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
+import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry;
 
 /**
@@ -57,12 +57,13 @@ public class PlateDimensionParser
     public static PlateDimension tryToGetPlateDimension(final IEntityProperty[] properties)
     {
         assert properties != null : "Unspecified properties";
-        final String plateGeometryString =
-                tryFindProperty(properties, PLATE_GEOMETRY_PROPERTY_NAME);
-        if (plateGeometryString == null)
+        IEntityProperty plateGeometryProperty =
+                EntityHelper.tryFindProperty(properties, PLATE_GEOMETRY_PROPERTY_NAME);
+        if (plateGeometryProperty == null)
         {
             return null;
         }
+        final String plateGeometryString = plateGeometryProperty.tryGetAsString();
         final PlateDimension dimension = tryParsePlateDimension(plateGeometryString);
         if (dimension == null)
         {
@@ -104,20 +105,6 @@ public class PlateDimensionParser
         }
     }
 
-    private static String tryFindProperty(final IEntityProperty[] properties,
-            final String propertyCode)
-    {
-        for (final IEntityProperty property : properties)
-        {
-            final PropertyType propertyType = property.getPropertyType();
-            if (propertyType.getCode().equals(propertyCode))
-            {
-                return property.tryGetAsString();
-            }
-        }
-        return null;
-    }
-
     public static Geometry getPlateGeometry(List<IEntityProperty> properties)
     {
         return getPlateDimension(properties.toArray(new IEntityProperty[0])).getPlateGeometry();
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTaskTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTaskTest.java
new file mode 100644
index 00000000000..70cc63560d1
--- /dev/null
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/LibraryRegistrationTaskTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2011 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.screening.server;
+
+import static org.hamcrest.text.StringContains.containsString;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.mail.From;
+import ch.systemsx.cisd.common.mail.IMailClient;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityTypeDAO;
+import ch.systemsx.cisd.openbis.generic.shared.ICommonServer;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.GenericEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListMaterialCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.util.EntityHelper;
+import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
+
+/**
+ * @author Kaloyan Enimanev
+ */
+public class LibraryRegistrationTaskTest extends AssertJUnit
+{
+    private static final String SESSION_TOKEN = "session";
+
+    private static final String USER_EMAIL = "micky.mouse@acme.org";
+
+    private Mockery context;
+
+    private ICommonServer commonServer;
+
+    private IGenericServer genericServer;
+
+    private IDAOFactory daoFactory;
+
+    private IEntityTypeDAO entityTypeDAO;
+
+    private IMailClient mailClient;
+
+    private LibraryRegistrationTask task;
+
+
+    @BeforeMethod
+    public void setUp()
+    {
+        context = new Mockery();
+        commonServer = context.mock(ICommonServer.class);
+        genericServer = context.mock(IGenericServer.class);
+        daoFactory = context.mock(IDAOFactory.class);
+        entityTypeDAO = context.mock(IEntityTypeDAO.class);
+        mailClient = context.mock(IMailClient.class);
+    }
+
+    @AfterMethod
+    public void tearDown()
+    {
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testUpdateGeneSymbols()
+    {
+        NewMaterial g1 = createNewGene("G1", "AB");
+        NewMaterial g2 = createNewGene("G2", "AB");
+        NewMaterial g3 = createNewGene("G3", "AB");
+        NewMaterial g4 = createNewGene("G4", "AB");
+        NewMaterial g5 = createNewGene("G5", "AB");
+
+        final List<NewMaterial> newGenes = Arrays.asList(g1, g2, g3, g4, g5);
+        final List<Material> existingGenes =
+                Arrays.asList(createExistingGene("G1", "ABC A"), createExistingGene("G2", "AB"),
+                        createExistingGene("G3", "AB BC"), createExistingGene("G4", "XY AB"),
+                        createExistingGene("G5", "XY AB YZ"));
+
+        task =
+                new LibraryRegistrationTask(SESSION_TOKEN, USER_EMAIL, newGenes, null, null,
+                        commonServer, genericServer, daoFactory, mailClient);
+
+        context.checking(new Expectations()
+            {
+                {
+                    one(daoFactory).getEntityTypeDAO(EntityKind.MATERIAL);
+                    will(returnValue(entityTypeDAO));
+
+                    one(entityTypeDAO).tryToFindEntityTypeByCode(
+                            ScreeningConstants.GENE_PLUGIN_TYPE_CODE);
+                    will(returnValue(new MaterialTypePE()));
+
+                    one(commonServer).listMaterials(with(SESSION_TOKEN),
+                            with(any(ListMaterialCriteria.class)), with(true));
+                    will(returnValue(existingGenes));
+                    
+                    one(genericServer).registerOrUpdateMaterials(SESSION_TOKEN,
+                            ScreeningConstants.GENE_PLUGIN_TYPE_CODE, newGenes);
+
+                    String[] emailTo = new String[]
+                        { USER_EMAIL };
+                    one(mailClient).sendMessage(with(containsString("success")),
+                            with(any(String.class)),
+                            with(aNull(String.class)), with(aNull(From.class)), with(emailTo));
+                }
+            });
+
+        task.run();
+
+        assertEquals("ABC A AB", extractGeneSymbol(g1));
+        assertEquals("AB", extractGeneSymbol(g2));
+        assertEquals("AB BC", extractGeneSymbol(g3));
+        assertEquals("XY AB", extractGeneSymbol(g4));
+        assertEquals("XY AB YZ", extractGeneSymbol(g5));
+    }
+
+    private NewMaterial createNewGene(String code, String geneSymbol)
+    {
+        IEntityProperty geneSymbolProperty = createGeneSymbolProperty(geneSymbol);
+        NewMaterial gene = new NewMaterial(code);
+        gene.setProperties(new IEntityProperty[]
+            { geneSymbolProperty });
+
+        return gene;
+    }
+
+    private Material createExistingGene(String code, String geneSymbol)
+    {
+        IEntityProperty geneSymbolProperty = createGeneSymbolProperty(geneSymbol);
+        Material gene = new Material();
+        gene.setCode(code);
+        gene.setMaterialType(new MaterialType());
+        gene.setProperties(Arrays.asList(geneSymbolProperty));
+        return gene;
+    }
+
+    private IEntityProperty createGeneSymbolProperty(String geneSymbol)
+    {
+        PropertyType propType = new PropertyType();
+        propType.setDataType(new DataType(DataTypeCode.MATERIAL));
+        propType.setCode(ScreeningConstants.GENE_SYMBOLS);
+
+        GenericEntityProperty entityProperty = new GenericEntityProperty();
+        entityProperty.setPropertyType(propType);
+        entityProperty.setValue(geneSymbol);
+        return entityProperty;
+    }
+
+    private String extractGeneSymbol(NewMaterial newMaterial)
+    {
+        IEntityProperty property =
+                EntityHelper.tryFindProperty(newMaterial.getProperties(),
+                        ScreeningConstants.GENE_SYMBOLS);
+        return property.getValue();
+    }
+}
-- 
GitLab