From 0d77fd303e5ab1a2aabca9953375489ef76252f7 Mon Sep 17 00:00:00 2001
From: buczekp <buczekp>
Date: Thu, 29 Apr 2010 12:02:33 +0000
Subject: [PATCH] [LMS-1507] improved speed of updating archiving statuses

SVN: 15679
---
 .../ch/systemsx/cisd/etlserver/ETLDaemon.java |   6 +-
 .../server/EncapsulatedOpenBISService.java    |  22 +--
 .../server/plugins/demo/DemoArchiver.java     |   6 +-
 .../AbstractArchiverProcessingPlugin.java     |  25 ++--
 .../shared/IEncapsulatedOpenBISService.java   |   6 +-
 .../QueueingDataSetStatusUpdaterService.java  |  53 ++++----
 ...tatus.java => DataSetCodesWithStatus.java} |  15 ++-
 .../openbis/generic/server/ETLService.java    |   6 +-
 .../generic/server/ETLServiceLogger.java      |   6 +-
 .../server/business/bo/ExternalDataBO.java    |   6 +-
 .../server/business/bo/ExternalDataTable.java |  38 +++---
 .../server/business/bo/IExternalDataBO.java   |   6 +-
 .../server/dataaccess/IExternalDataDAO.java   |   8 +-
 .../server/dataaccess/db/ExternalDataDAO.java | 126 ++++++++++++++++--
 .../generic/shared/IETLLIMSService.java       |  17 +--
 .../business/bo/ExternalDataBOTest.java       |  13 +-
 .../business/bo/ExternalDataTableTest.java    |   2 +-
 17 files changed, 239 insertions(+), 122 deletions(-)
 rename datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/{DataSetCodeWithStatus.java => DataSetCodesWithStatus.java} (74%)

diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/ETLDaemon.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/ETLDaemon.java
index 5e5304dff65..8cee248982b 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/ETLDaemon.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/ETLDaemon.java
@@ -64,7 +64,7 @@ import ch.systemsx.cisd.openbis.dss.BuildAndEnvironmentInfo;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.QueueingDataSetStatusUpdaterService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider;
-import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetCodeWithStatus;
+import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetCodesWithStatus;
 import ch.systemsx.cisd.openbis.dss.generic.shared.utils.PropertyParametersUtil;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseInstance;
@@ -122,7 +122,7 @@ public final class ETLDaemon
 
     public static void listUpdaterQueue()
     {
-        final List<DataSetCodeWithStatus> items =
+        final List<DataSetCodesWithStatus> items =
                 QueueingDataSetStatusUpdaterService.listItems(updaterQueueFile);
         if (items.isEmpty())
         {
@@ -130,7 +130,7 @@ public final class ETLDaemon
         } else
         {
             System.out.println("Found " + items.size() + " items in updater:");
-            for (final DataSetCodeWithStatus item : items)
+            for (final DataSetCodesWithStatus item : items)
             {
                 System.out.println(item);
             }
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 5c5eb985b4f..90bfb4102c6 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
@@ -308,7 +308,8 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
         }
     }
 
-    synchronized public ExperimentType getExperimentType(String experimentTypeCode) throws UserFailureException
+    synchronized public ExperimentType getExperimentType(String experimentTypeCode)
+            throws UserFailureException
     {
         checkSessionToken();
         try
@@ -408,7 +409,8 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
         }
     }
 
-    synchronized public void updateSample(SampleUpdatesDTO sampleUpdate) throws UserFailureException
+    synchronized public void updateSample(SampleUpdatesDTO sampleUpdate)
+            throws UserFailureException
     {
         assert sampleUpdate != null : "Unspecified sample.";
 
@@ -474,31 +476,31 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
         service.addPropertiesToDataSet(sessionToken, properties, code, space);
     }
 
-    synchronized public final void updateDataSetStatus(String code, DataSetArchivingStatus newStatus)
-            throws UserFailureException
+    synchronized public final void updateDataSetStatuses(List<String> codes,
+            DataSetArchivingStatus newStatus) throws UserFailureException
 
     {
-        assert code != null : "missing data set code";
+        assert codes != null : "missing data set codes";
         assert newStatus != null : "missing status";
 
         checkSessionToken();
         try
         {
-            primUpdateDataSetStatus(code, newStatus);
+            primUpdateDataSetStatuses(codes, newStatus);
         } catch (final InvalidSessionException ex)
         {
             authenticate();
-            primUpdateDataSetStatus(code, newStatus);
+            primUpdateDataSetStatuses(codes, newStatus);
         }
         if (operationLog.isInfoEnabled())
         {
-            operationLog.info("Updated in openBIS: data set " + code + ", status=" + newStatus);
+            operationLog.info("Updated in openBIS: data sets " + codes + ", status=" + newStatus);
         }
     }
 
-    private void primUpdateDataSetStatus(String code, DataSetArchivingStatus newStatus)
+    private void primUpdateDataSetStatuses(List<String> codes, DataSetArchivingStatus newStatus)
     {
-        service.updateDataSetStatus(sessionToken, code, newStatus);
+        service.updateDataSetStatuses(sessionToken, codes, newStatus);
     }
 
     synchronized public final IEntityProperty[] getPropertiesOfTopSampleRegisteredFor(
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoArchiver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoArchiver.java
index 509af5bba2a..4e6b846d4ce 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoArchiver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoArchiver.java
@@ -21,7 +21,6 @@ import java.util.Properties;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.AbstractArchiverProcessingPlugin;
-import ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.HighWaterMarkChecker;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
 
 /**
@@ -33,8 +32,9 @@ public class DemoArchiver extends AbstractArchiverProcessingPlugin
 
     public DemoArchiver(Properties properties, File storeRoot)
     {
-        super(properties, storeRoot, new HighWaterMarkChecker(storeRoot), new HighWaterMarkChecker(
-                storeRoot));
+        super(properties, storeRoot, null, null);
+        // NOTE using HighWaterMarkChecker before archiving every dataset degrades performance
+        // super(properties, storeRoot, new HighWaterMarkChecker(storeRoot), new HighWaterMarkChecker(storeRoot);
     }
 
     @Override
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java
index 0b93ede47f1..5002ecaec9a 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
 
@@ -30,7 +31,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ProcessDatasetsCommand;
 import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.IArchiverTask;
 import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.ProcessingStatus;
 import ch.systemsx.cisd.openbis.dss.generic.shared.QueueingDataSetStatusUpdaterService;
-import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetCodeWithStatus;
+import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetCodesWithStatus;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetArchivingStatus;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
 
@@ -66,8 +67,7 @@ public abstract class AbstractArchiverProcessingPlugin extends AbstractDatastore
 
     public ProcessingStatus archive(List<DatasetDescription> datasets)
     {
-        operationLog
-                .info("Archiving of the following datasets has been requested: " + datasets);
+        operationLog.info("Archiving of the following datasets has been requested: " + datasets);
         return handleDatasets(datasets, DataSetArchivingStatus.ARCHIVED,
                 DataSetArchivingStatus.AVAILABLE, new IDatasetDescriptionHandler()
                     {
@@ -97,8 +97,7 @@ public abstract class AbstractArchiverProcessingPlugin extends AbstractDatastore
 
     public ProcessingStatus unarchive(List<DatasetDescription> datasets)
     {
-        operationLog.info("Unarchiving of the following datasets has been requested: "
-                + datasets);
+        operationLog.info("Unarchiving of the following datasets has been requested: " + datasets);
         return handleDatasets(datasets, DataSetArchivingStatus.AVAILABLE,
                 DataSetArchivingStatus.ARCHIVED, new IDatasetDescriptionHandler()
                     {
@@ -131,17 +130,27 @@ public abstract class AbstractArchiverProcessingPlugin extends AbstractDatastore
             IDatasetDescriptionHandler handler)
     {
         final ProcessingStatus result = new ProcessingStatus();
+        List<String> successful = new ArrayList<String>();
+        List<String> failed = new ArrayList<String>();
         for (DatasetDescription dataset : datasets)
         {
             Status status = handler.handle(dataset);
-            DataSetArchivingStatus newStatus = status.isError() ? failure : success;
-            QueueingDataSetStatusUpdaterService.update(new DataSetCodeWithStatus(dataset
-                    .getDatasetCode(), newStatus));
+            List<String> codes = status.isError() ? failed : successful;
+            codes.add(dataset.getDatasetCode());
             result.addDatasetStatus(dataset, status);
         }
+        asyncUpdateStatuses(successful, success);
+        asyncUpdateStatuses(failed, failure);
         return result;
     }
 
+    private static void asyncUpdateStatuses(List<String> dataSetCodes,
+            DataSetArchivingStatus newStatus)
+    {
+        QueueingDataSetStatusUpdaterService.update(new DataSetCodesWithStatus(dataSetCodes,
+                newStatus));
+    }
+
     private interface IDatasetDescriptionHandler
     {
         public Status handle(DatasetDescription dataset);
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 fdcb57eba66..f7e2ecae3bf 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
@@ -89,7 +89,7 @@ public interface IEncapsulatedOpenBISService
      * Gets the experiment type with assigned property types for the specified experiment type code.
      */
     public ExperimentType getExperimentType(String experimentTypeCode) throws UserFailureException;
-    
+
     /**
      * Gets the sample type with assigned property types for the specified sample type code.
      */
@@ -208,7 +208,7 @@ public interface IEncapsulatedOpenBISService
     /** See {@link IETLLIMSService#unarchiveDatasets(String, List)} */
     public void unarchiveDataSets(List<String> dataSetCodes) throws UserFailureException;
 
-    /** See {@link IETLLIMSService#updateDataSetStatus(String, String, DataSetArchivingStatus)} */
-    public void updateDataSetStatus(String code, DataSetArchivingStatus newStatus)
+    /** See {@link IETLLIMSService#updateDataSetStatuses(String, List, DataSetArchivingStatus)} */
+    public void updateDataSetStatuses(List<String> codes, DataSetArchivingStatus newStatus)
             throws UserFailureException;
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/QueueingDataSetStatusUpdaterService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/QueueingDataSetStatusUpdaterService.java
index 1d419b2b579..5ead3ce28c7 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/QueueingDataSetStatusUpdaterService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/QueueingDataSetStatusUpdaterService.java
@@ -33,12 +33,12 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.filesystem.ICloseable;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
-import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetCodeWithStatus;
+import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetCodesWithStatus;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetArchivingStatus;
 
 /**
  * A service for updating data set status in openBIS. It provides a method
- * {@link #update(DataSetCodeWithStatus)} that queues updates using a separate thread to actually
+ * {@link #update(DataSetCodesWithStatus)} that queues updates using a separate thread to actually
  * perform update.
  * <p>
  * Note that the service needs to be started via {@link #start(File, TimingParameters)}.
@@ -62,7 +62,7 @@ public class QueueingDataSetStatusUpdaterService
     @Private
     final static String UPDATER_PREFIX = ".UPDATER_";
 
-    private static IExtendedBlockingQueue<DataSetCodeWithStatus> queue = null;
+    private static IExtendedBlockingQueue<DataSetCodesWithStatus> queue = null;
 
     private static ICloseable queueCloseableOrNull = null;
 
@@ -91,7 +91,7 @@ public class QueueingDataSetStatusUpdaterService
      */
     public static synchronized final void start(final File queueFile, TimingParameters parameters)
     {
-        final PersistentExtendedBlockingQueueDecorator<DataSetCodeWithStatus> persistentQueue =
+        final PersistentExtendedBlockingQueueDecorator<DataSetCodesWithStatus> persistentQueue =
                 ExtendedBlockingQueueFactory.createPersistRecordBased(queueFile,
                         INITIAL_RECORD_SIZE);
         queue = persistentQueue;
@@ -105,10 +105,10 @@ public class QueueingDataSetStatusUpdaterService
                     {
                         while (true)
                         {
-                            final DataSetCodeWithStatus dataSet = queue.peekWait();
+                            final DataSetCodesWithStatus dataSets = queue.peekWait();
                             try
                             {
-                                updater.updateDataSetStatus(dataSet.getDataSetCode(), dataSet
+                                updater.updateDataSetStatuses(dataSets.getDataSetCodes(), dataSets
                                         .getStatus());
                                 // Note: this is the only consumer of this queue.
                                 queue.take();
@@ -121,16 +121,16 @@ public class QueueingDataSetStatusUpdaterService
                                 // If connection with openBIS fails it is not possible
                                 // the same problem will occur for other updates in the queue,
                                 // so we just retry after increasing time.
-                                notifyUpdateFailure(dataSet, ex);
+                                notifyUpdateFailure(dataSets, ex);
                                 Sleeper.sleepAndIncreaseSleepTime();
                             } catch (UserFailureException ex)
                             {
                                 // OpenBIS failure occurred - the problem may be connected with
                                 // certain data set so move this item to the end of the queue and
                                 // try to update other data sets before retrying.
-                                notifyUpdateFailure(dataSet, ex);
+                                notifyUpdateFailure(dataSets, ex);
                                 Sleeper.sleepAndIncreaseSleepTime();
-                                queue.add(dataSet);
+                                queue.add(dataSets);
                                 queue.remove();
                             }
                         }
@@ -143,10 +143,10 @@ public class QueueingDataSetStatusUpdaterService
                     }
                 }
 
-                private void notifyUpdateFailure(final DataSetCodeWithStatus dataSet, Exception ex)
+                private void notifyUpdateFailure(final DataSetCodesWithStatus dataSets, Exception ex)
                 {
-                    notificationLog.error("Update of data set " + dataSet.getDataSetCode()
-                            + " status to '" + dataSet.getStatus()
+                    notificationLog.error("Update of data sets " + dataSets.getDataSetCodes()
+                            + " status to '" + dataSets.getStatus()
                             + "' has failed.\nRetry will occur not sooner than "
                             + Sleeper.getCurrentSleepTime() + ".", ex);
                 }
@@ -159,23 +159,27 @@ public class QueueingDataSetStatusUpdaterService
     {
         return new IDataSetStatusUpdater()
             {
-                public void updateDataSetStatus(String dataSetCode,
+                public void updateDataSetStatuses(List<String> dataSetCodes,
                         DataSetArchivingStatus newStatus)
                 {
-                    ServiceProvider.getOpenBISService().updateDataSetStatus(dataSetCode, newStatus);
-                    operationLog
-                            .info("Data Set " + dataSetCode + " changed status to " + newStatus);
+                    ServiceProvider.getOpenBISService().updateDataSetStatuses(dataSetCodes,
+                            newStatus);
+                    operationLog.info("Data Sets " + dataSetCodes + " changed status to "
+                            + newStatus);
                 }
 
             };
     }
 
     /**
-     * Schedules update of given data set. If operation fails the updating thread will exit.
+     * Schedules update of given data sets.
      */
-    public static void update(DataSetCodeWithStatus dataSet)
+    public static void update(DataSetCodesWithStatus dataSets)
     {
-        queue.add(dataSet);
+        if (dataSets.getDataSetCodes().isEmpty() == false)
+        {
+            queue.add(dataSets);
+        }
     }
 
     private static final void close()
@@ -242,9 +246,9 @@ public class QueueingDataSetStatusUpdaterService
     /**
      * Returns the list of currently queued up items.
      */
-    public static final List<DataSetCodeWithStatus> listItems(File queueFile)
+    public static final List<DataSetCodesWithStatus> listItems(File queueFile)
     {
-        return RecordBasedQueuePersister.list(DataSetCodeWithStatus.class, queueFile);
+        return RecordBasedQueuePersister.list(DataSetCodesWithStatus.class, queueFile);
     }
 
     private QueueingDataSetStatusUpdaterService()
@@ -304,12 +308,13 @@ public class QueueingDataSetStatusUpdaterService
     public interface IDataSetStatusUpdater
     {
         /**
-         * Updates status of data set with given code.
+         * Updates status of data sets with given codes.
          * 
-         * @param dataSetCode code of data set to be updated
+         * @param dataSetCodes codes of data sets to be updated
          * @param newStatus status to be set
          */
-        public void updateDataSetStatus(String dataSetCode, DataSetArchivingStatus newStatus);
+        public void updateDataSetStatuses(List<String> dataSetCodes,
+                DataSetArchivingStatus newStatus);
     }
 
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetCodeWithStatus.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetCodesWithStatus.java
similarity index 74%
rename from datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetCodeWithStatus.java
rename to datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetCodesWithStatus.java
index 26e6b72d386..5a72c8ea761 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetCodeWithStatus.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetCodesWithStatus.java
@@ -17,29 +17,30 @@
 package ch.systemsx.cisd.openbis.dss.generic.shared.dto;
 
 import java.io.Serializable;
+import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetArchivingStatus;
 
 /**
  * @author Piotr Buczek
  */
-public class DataSetCodeWithStatus implements Serializable
+public class DataSetCodesWithStatus implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
-    private String dataSetCode;
+    private List<String> dataSetCodes;
 
     private DataSetArchivingStatus status;
 
-    public DataSetCodeWithStatus(String dataSetCode, DataSetArchivingStatus status)
+    public DataSetCodesWithStatus(List<String> dataSetCodes, DataSetArchivingStatus status)
     {
-        this.dataSetCode = dataSetCode;
+        this.dataSetCodes = dataSetCodes;
         this.status = status;
     }
 
-    public String getDataSetCode()
+    public List<String> getDataSetCodes()
     {
-        return dataSetCode;
+        return dataSetCodes;
     }
 
     public DataSetArchivingStatus getStatus()
@@ -50,7 +51,7 @@ public class DataSetCodeWithStatus implements Serializable
     @Override
     public String toString()
     {
-        return dataSetCode + " - " + getStatus();
+        return dataSetCodes + " - " + getStatus();
     }
 
 }
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 a3f43f8d136..a3d24afdab1 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
@@ -438,7 +438,7 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
             throws UserFailureException
     {
         final Session session = getSession(sessionToken);
-        
+
         IExternalDataTable dataSetTable = businessObjectFactory.createExternalDataTable(session);
         dataSetTable.loadByExperimentTechId(experimentID);
         return ExternalDataTranslator.translate(dataSetTable.getExternalData(), "", "");
@@ -589,13 +589,13 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
         externalDataBO.addPropertiesToDataSet(dataSetCode, properties);
     }
 
-    public void updateDataSetStatus(String sessionToken, String dataSetCode,
+    public void updateDataSetStatuses(String sessionToken, List<String> dataSetCodes,
             DataSetArchivingStatus newStatus) throws UserFailureException
     {
         assert sessionToken != null : "Unspecified session token.";
         final Session session = getSession(sessionToken);
         final IExternalDataBO externalDataBO = businessObjectFactory.createExternalDataBO(session);
-        externalDataBO.updateStatus(dataSetCode, newStatus);
+        externalDataBO.updateStatuses(dataSetCodes, newStatus);
     }
 
     public ExternalData tryGetDataSet(String sessionToken, String dataSetCode)
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 6e12684d691..775e7da6781 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
@@ -252,11 +252,11 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
                 properties.size());
     }
 
-    public void updateDataSetStatus(String sessionToken, String dataSetCode,
+    public void updateDataSetStatuses(String sessionToken, List<String> dataSetCodes,
             DataSetArchivingStatus newStatus) throws UserFailureException
     {
-        logTracking(sessionToken, "updateDataSetStatus", "DATA_SET_CODE(%s) STATUS(%s)",
-                dataSetCode, newStatus);
+        logTracking(sessionToken, "updateDataSetStatus", "DATA_SET_CODES(%s) STATUS(%s)",
+                dataSetCodes, newStatus);
     }
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBO.java
index 94cb5b5dfcf..f4ae211db37 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBO.java
@@ -102,7 +102,7 @@ public class ExternalDataBO extends AbstractExternalDataBusinessObject implement
         loadByCode(dataSetCode, true, false);
     }
 
-    public void loadByCode(String dataSetCode, boolean withPropertyTypes, boolean lockForUpdate)
+    private void loadByCode(String dataSetCode, boolean withPropertyTypes, boolean lockForUpdate)
     {
         externalData =
                 getExternalDataDAO().tryToFindFullDataSetByCode(dataSetCode, withPropertyTypes,
@@ -662,9 +662,9 @@ public class ExternalDataBO extends AbstractExternalDataBusinessObject implement
         return result;
     }
 
-    public void updateStatus(String dataSetCode, DataSetArchivingStatus newStatus)
+    public void updateStatuses(List<String> dataSetCodes, DataSetArchivingStatus newStatus)
     {
-        getExternalDataDAO().updateDataSetStatus(dataSetCode, newStatus);
+        getExternalDataDAO().updateDataSetStatuses(dataSetCodes, newStatus);
     }
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTable.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTable.java
index 05260ac6020..7601abf7deb 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTable.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTable.java
@@ -163,7 +163,7 @@ public final class ExternalDataTable extends AbstractExternalDataBusinessObject
         IExternalDataDAO externalDataDAO = getExternalDataDAO();
 
         externalData = new ArrayList<ExternalDataPE>();
-        externalData.addAll(externalDataDAO.tryToFindFullDataSetByCodes(dataSetCodes,
+        externalData.addAll(externalDataDAO.tryToFindFullDataSetsByCodes(dataSetCodes,
                 withProperties, lockForUpdate));
     }
 
@@ -434,24 +434,21 @@ public final class ExternalDataTable extends AbstractExternalDataBusinessObject
         return service.createReportFromDatasets(sessionToken, datastoreServiceKey, locations);
     }
 
-    private List<DatasetDescription> loadAvailableDatasetDescriptions(List<String> datasetCodes)
+    private List<DatasetDescription> loadAvailableDatasetDescriptions(List<String> dataSetCodes)
     {
         IExternalDataDAO externalDataDAO = getExternalDataDAO();
         List<DatasetDescription> result = new ArrayList<DatasetDescription>();
         List<String> notAvailableDatasets = new ArrayList<String>();
-        for (String datasetCode : datasetCodes)
+        List<ExternalDataPE> dataSets =
+                externalDataDAO.tryToFindFullDataSetsByCodes(dataSetCodes, false, false);
+        for (ExternalDataPE dataSet : dataSets)
         {
-            ExternalDataPE dataSet =
-                    externalDataDAO.tryToFindFullDataSetByCode(datasetCode, false, false);
-            if (dataSet != null)
+            if (dataSet.getStatus().isAvailable())
             {
-                if (dataSet.getStatus().isAvailable())
-                {
-                    result.add(createDatasetDescription(dataSet));
-                } else
-                {
-                    notAvailableDatasets.add(datasetCode);
-                }
+                result.add(createDatasetDescription(dataSet));
+            } else
+            {
+                notAvailableDatasets.add(dataSet.getCode());
             }
         }
         throwUnavailableOperationExceptionIfNecessary(notAvailableDatasets);
@@ -547,7 +544,7 @@ public final class ExternalDataTable extends AbstractExternalDataBusinessObject
     private int filterByStatusAndUpdate(Map<DataStorePE, List<ExternalDataPE>> datasetsByStore,
             DataSetArchivingStatus oldStatus, DataSetArchivingStatus newStatus)
     {
-        int counter = 0;
+        List<String> codesToUpdate = new ArrayList<String>();
         IExternalDataDAO externalDataDAO = getExternalDataDAO();
         for (List<ExternalDataPE> dataSets : datasetsByStore.values())
         {
@@ -560,14 +557,17 @@ public final class ExternalDataTable extends AbstractExternalDataBusinessObject
                     iterator.remove();
                 } else
                 {
-                    dataSet.setStatus(newStatus);
-                    externalDataDAO.validate(dataSet);
-                    counter++;
+                    codesToUpdate.add(dataSet.getCode());
                 }
             }
         }
-        externalDataDAO.flush();
-        return counter;
+        // WORKAROUND In order not to load data set properties at the end of transaction
+        // for Hibernate Search indexing, we don't make change to PE's loaded by Hibernate,
+        // but perform a 'bulk update' operation. Such an operation is quicker and Hibernate
+        // Search doesn't spot the change. The drawback is that the loaded objects are
+        // not updated with a new status.
+        externalDataDAO.updateDataSetStatuses(codesToUpdate, newStatus);
+        return codesToUpdate.size();
     }
 
     private interface IArchivingAction
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExternalDataBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExternalDataBO.java
index 28bb7046803..c7440658fa6 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExternalDataBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IExternalDataBO.java
@@ -68,11 +68,11 @@ public interface IExternalDataBO extends IEntityBusinessObject
     public void update(DataSetUpdatesDTO updates);
 
     /**
-     * Updates status of given data set.
+     * Updates status of given data sets.
      * 
-     * @throws UserFailureException if data set does not exist or status couldn't be set.
+     * @throws UserFailureException if a data set does not exist or status couldn't be set.
      */
-    public void updateStatus(String dataSetCode, DataSetArchivingStatus newStatus)
+    public void updateStatuses(List<String> dataSetCodes, DataSetArchivingStatus newStatus)
             throws UserFailureException;
 
     /**
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExternalDataDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExternalDataDAO.java
index 00b4cf18765..7e419c56bbd 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExternalDataDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExternalDataDAO.java
@@ -81,15 +81,15 @@ public interface IExternalDataDAO extends IGenericDAO<ExternalDataPE>
             boolean lockForUpdate);
 
     /**
-     * Tries to get the full data set for the specified code with optional locking.
+     * Tries to get the full data sets for the specified codes with optional locking.
      */
-    public List<ExternalDataPE> tryToFindFullDataSetByCodes(Collection<String> dataSetCodes,
+    public List<ExternalDataPE> tryToFindFullDataSetsByCodes(Collection<String> dataSetCodes,
             boolean withPropertyTypes, boolean lockForUpdate);
 
     /**
-     * Sets status of dataset with given code.
+     * Sets status of datasets with given codes.
      */
-    public void updateDataSetStatus(String dataSetCodes, DataSetArchivingStatus status);
+    public void updateDataSetStatuses(List<String> dataSetCodes, DataSetArchivingStatus status);
 
     /**
      * Persists the specified data set.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExternalDataDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExternalDataDAO.java
index e6e07ce9b5f..6f845885d8d 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExternalDataDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExternalDataDAO.java
@@ -16,15 +16,19 @@
 
 package ch.systemsx.cisd.openbis.generic.server.dataaccess.db;
 
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
 import org.apache.log4j.Logger;
 import org.hibernate.FetchMode;
+import org.hibernate.HibernateException;
 import org.hibernate.LockMode;
 import org.hibernate.Session;
 import org.hibernate.SessionFactory;
@@ -49,6 +53,8 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.CodeConverter;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescriptionPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
@@ -203,11 +209,86 @@ final class ExternalDataDAO extends AbstractGenericEntityDAO<ExternalDataPE> imp
         return entity;
     }
 
-    public List<ExternalDataPE> tryToFindFullDataSetByCodes(Collection<String> dataSetCodes,
+    public List<DatasetDescription> tryToFindDatasetDescriptionsByCodes(List<String> dataSetCodes)
+    {
+        assert dataSetCodes != null : "Unspecified data set code";
+
+        // final Criterion codeEq = Restrictions.eq("code", mangledCode);
+
+        // TODO
+        // Hibernate bug (HHH-2676) - can't take a lock in this kind of query.
+        String queryString =
+                "select new ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription(d.code, d.location, d.sampleInternal.code, "
+                        + "d.experimentInternal.projectInternal.group.code, "
+                        + "d.experimentInternal.projectInternal.code, "
+                        + "d.experimentInternal.code, "
+                        + "d.dataSetType.mainDataSetPath, d.dataSetType.mainDataSetPattern, "
+                        + "d.experimentInternal.projectInternal.group.databaseInstance.code) "
+                        + "from " + TABLE_NAME + " d where d.code IN (:codes)";
+
+        Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();
+        final List<DatasetDescription> list =
+                cast(session.createQuery(queryString).setParameterList("codes", dataSetCodes)
+                        .list());
+ 
+        System.err.println(list.size());
+        for (DatasetDescription desc : list)
+        {
+            System.err.println(ReflectionToStringBuilder.toString(desc));
+        }
+        // if (operationLog.isDebugEnabled())
+        // {
+        // operationLog.debug(String.format("External data '%s' found for data set code '%s'.",
+        // entity, dataSetCode));
+        // }
+        return list;
+    }
+
+    public List<DatasetDescriptionPE> tryToFindDatasetDescriptionsByCodes(List<String> dataSetCodes)
+    {
+        assert dataSetCodes != null : "Unspecified data set code";
+
+        // final Criterion codeEq = Restrictions.eq("code", mangledCode);
+
+        // TODO
+        // Hibernate bug (HHH-2676) - can't take a lock in this kind of query.
+        String queryString =
+                "select new DatasetDescriptionPE(d.code, d.location, d.sampleInternal.code, "
+                        + "d.experimentInternal.projectInternal.group.code, "
+                        + "d.experimentInternal.projectInternal.code, "
+                        + "d.experimentInternal.code, "
+                        + "d.dataSetType.mainDataSetPath, d.dataSetType.mainDataSetPattern, "
+                        + "d.experimentInternal.projectInternal.group.databaseInstance.code) "
+                        + "from " + TABLE_NAME + " d where d.code IN (:codes)";
+
+        Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();
+        final List<DatasetDescriptionPE> list =
+                cast(session.createQuery(queryString).setParameterList("codes", dataSetCodes)
+                        .list());
+
+        System.err.println(list.size());
+        for (DatasetDescriptionPE desc : list)
+        {
+            System.err.println(ReflectionToStringBuilder.toString(desc));
+        }
+        // if (operationLog.isDebugEnabled())
+        // {
+        // operationLog.debug(String.format("External data '%s' found for data set code '%s'.",
+        // entity, dataSetCode));
+        // }
+        return list;
+    }
+
+    public List<ExternalDataPE> tryToFindFullDataSetsByCodes(Collection<String> dataSetCodes,
             boolean withPropertyTypes, boolean lockForUpdate)
     {
         assert dataSetCodes != null : "Unspecified collection";
 
+        if (dataSetCodes.size() == 0)
+        {
+            return Collections.emptyList();
+        }
+
         final Criterion codeIn = Restrictions.in("code", dataSetCodes);
 
         final DetachedCriteria criteria = DetachedCriteria.forClass(ENTITY_CLASS);
@@ -271,28 +352,45 @@ final class ExternalDataDAO extends AbstractGenericEntityDAO<ExternalDataPE> imp
         return entity;
     }
 
-    public void updateDataSetStatus(String dataSetCode, final DataSetArchivingStatus status)
+    public void updateDataSetStatuses(final List<String> dataSetCodes,
+            final DataSetArchivingStatus status)
     {
-        assert dataSetCode != null : "Unspecified data set code";
+        assert dataSetCodes != null : "Unspecified data set codes";
         assert status != null : "Unspecified code";
 
-        final String mangledCode = CodeConverter.tryToDatabase(dataSetCode);
+        if (dataSetCodes.size() == 0)
+        {
+            return;
+        }
 
         final HibernateTemplate hibernateTemplate = getHibernateTemplate();
-        // NOTE: 'VERSIONED' makes modification time modified too
-        final int updatedRows =
-                hibernateTemplate.bulkUpdate("UPDATE VERSIONED " + TABLE_NAME
-                        + " SET status = ? WHERE code = ? ", toArray(status, mangledCode));
+        int updatedRows = (Integer) hibernateTemplate.execute(new HibernateCallback()
+            {
+
+                //
+                // HibernateCallback
+                //
+
+                public final Object doInHibernate(final Session session) throws HibernateException,
+                        SQLException
+                {
+                    // NOTE: 'VERSIONED' makes modification time modified too
+                    return session.createQuery(
+                            "UPDATE VERSIONED " + TABLE_NAME
+                                    + " SET status = :status WHERE code IN (:codes) ")
+                            .setParameter("status", status).setParameterList("codes", dataSetCodes)
+                            .executeUpdate();
+                }
+            });
         hibernateTemplate.flush();
-
-        if (updatedRows == 0)
+        if (updatedRows != dataSetCodes.size())
         {
-            throw UserFailureException.fromTemplate("Update of dataset's %s status to %s failed.",
-                    dataSetCode, status);
+            throw UserFailureException.fromTemplate("Update of %s data set statuses to %s failed.",
+                    dataSetCodes.size(), status);
         } else if (operationLog.isInfoEnabled())
         {
-            operationLog.info(String.format("UPDATE: external data '%s' status %s.", dataSetCode,
-                    status));
+            operationLog.info(String.format("UPDATED: %s data set statuses to '%s'.", dataSetCodes
+                    .size(), status));
         }
     }
 
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 1b12e1e98e0..034ebd44fcd 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
@@ -114,20 +114,21 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             throws UserFailureException;
 
     /**
-     * Returns the ExperimentType together with assigned property types for specified experiment type code.
+     * Returns the ExperimentType together with assigned property types for specified experiment
+     * type code.
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleSet.ETL_SERVER)
     public ExperimentType getExperimentType(String sessionToken, String experimentTypeCode)
             throws UserFailureException;
-    
+
     /**
      * Returns the SampleType together with assigned property types for specified sample type code.
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleSet.ETL_SERVER)
     public SampleType getSampleType(String sessionToken, String sampleTypeCode)
-    throws UserFailureException;
+            throws UserFailureException;
 
     /**
      * Returns the data set type together with assigned property types for specified data set type
@@ -149,7 +150,7 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             final String sessionToken,
             @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class) final TechId experimentID)
             throws UserFailureException;
-    
+
     /**
      * For given sample {@link TechId} returns the corresponding list of {@link ExternalData}.
      * 
@@ -219,7 +220,7 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseUpdateModification(value = ObjectKind.SAMPLE)
     public void updateSample(String sessionToken,
             @AuthorizationGuard(guardClass = SampleUpdatesPredicate.class) SampleUpdatesDTO updates);
-    
+
     /**
      * Registers the specified data connected to a sample.
      * 
@@ -341,13 +342,13 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             throws UserFailureException;
 
     /**
-     * Updates status of given data set.
+     * Updates status of given data sets.
      */
     @Transactional
     @RolesAllowed(RoleSet.ETL_SERVER)
     @DatabaseUpdateModification(value = ObjectKind.DATA_SET)
-    public void updateDataSetStatus(String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode,
+    public void updateDataSetStatuses(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) List<String> dataSetCodes,
             final DataSetArchivingStatus newStatus) throws UserFailureException;
 
     /**
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBOTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBOTest.java
index f565024e9f1..6c00697d1fa 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBOTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataBOTest.java
@@ -19,9 +19,11 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo;
 import static ch.systemsx.cisd.openbis.generic.server.business.ManagerTestTool.EXAMPLE_SESSION;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.List;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
@@ -286,21 +288,20 @@ public class ExternalDataBOTest extends AbstractBOTest
     }
 
     @Test
-    public void testUpdateStatus()
+    public void testUpdateStatuses()
     {
-        final SamplePE sample = new SamplePE();
-        sample.setCode(SAMPLE_IDENTIFIER.getSampleCode());
-        final ExternalDataPE dataSet = createDataSet(sample, null);
+        final List<String> codes = Arrays.asList(new String[]
+            { "CODE-1", "CODE-2" });
         context.checking(new Expectations()
             {
                 {
-                    one(externalDataDAO).updateDataSetStatus(dataSet.getCode(),
+                    one(externalDataDAO).updateDataSetStatuses(codes,
                             DataSetArchivingStatus.ARCHIVED);
                 }
             });
 
         IExternalDataBO dataBO = createExternalDataBO();
-        dataBO.updateStatus(dataSet.getCode(), DataSetArchivingStatus.ARCHIVED);
+        dataBO.updateStatuses(codes, DataSetArchivingStatus.ARCHIVED);
         // TODO DAO test
         context.assertIsSatisfied();
     }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTableTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTableTest.java
index ac8be65ecf6..732285f6f44 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTableTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExternalDataTableTest.java
@@ -234,7 +234,7 @@ public final class ExternalDataTableTest extends AbstractBOTest
         context.checking(new Expectations()
             {
                 {
-                    one(externalDataDAO).tryToFindFullDataSetByCodes(
+                    one(externalDataDAO).tryToFindFullDataSetsByCodes(
                             Code.extractCodes(Arrays.asList(searched)), withProperties,
                             lockForUpdate);
                     will(returnValue(Arrays.asList(results)));
-- 
GitLab